И наконец, в разделе "Злые волшебники" говорится о том, что необходимо быть осторожным с инструментальными средствами, генерирующими миллионы строк от вашего имени, если вы не понимаете сути работы этих средств.
Многие из нас в значительной степени управляют автомобилем "на автопилоте" – мы не даем явных указаний ноге, чтобы она нажала на педаль, или руке, чтобы она повернула руль, а мысленно говорим себе: "снизить скорость и повернуть направо". Но дисциплинированные водители постоянно контролируют ситуацию, отыскивают потенциальные проблемы и оказываются в нужном положении, если происходит непредвиденное. Это применимо и к написанию программ – возможно, об этом говорилось уже много раз, но хладнокровие всегда позволит вам предотвратить катастрофу.
31
Программирование в расчете на стечение обстоятельств
Случалось ли вам когда-нибудь смотреть старые черно-белые фильмы о войне? Усталый солдат осторожно выбирается из зарослей кустарника. Впереди него свободное пространство, и солдат задается вопросом: есть ли впереди мины или можно безбоязненно идти дальше? Ничто не говорит о том, что впереди минное поле, – нет ни знаков, ни колючей проволоки, ни воронок. Солдат пробует штыком грунт впереди себя и вздрагивает в ожидании взрыва. Но ничего не происходит. Какое-то время он продолжает осторожно продвигаться но полю, прощупывая грунт. В конце концов, убедившись, что проход безопасен, он распрямляется и начинает гордо маршировать вперед… навстречу смерти.
Первые поиски мин, проведенные солдатом, были безрезультатны, но ему просто повезло. Он пришел к ложному заключению, которое закончилось катастрофой.
Программисты также работают на заминированной территории. Существуют сотни ловушек, подстерегающих нас ежедневно. Помня об истории с солдатом из фильма, нам стоит опасаться ложных заключений. Необходимо избегать программирования в расчете на стечение обстоятельств, полагаясь на удачу и случайные успехи, и сделать выбор в пользу преднамеренного программирования.
Как программировать в расчете на стечение обстоятельств
Предположим, Фреду дано задание написать программу. Фред составляет некую программу, пробует ее запустить, и она вроде бы работает. Фред пишет еще один фрагмент, пробует его запустить, и снова все работает. В такой обстановке проходит еще несколько недель, но внезапно программа прекращает работать, и, потратив несколько часов на устранение дефекта, Фред все еще не знает, в чем причина. Фред может потратить много времени, копаясь с этим фрагментом, без перспективы на восстановление работы программы. И что бы он ни делал, кажется, что программа никогда не будет работать правильно.
Фред не знает, почему программа сбоит, потому что не знает, почему она работала вначале. Она лишь казалась работающей в условиях ограниченного «тестирования», которое проводил Фред, но это было лишь стечением обстоятельств. Находясь в плену ложной уверенности, Фред впал в забытье. Большинству интеллектуалов знаком этот образ Фреда, но мы знаем его лучше. Мы ведь не полагаемся на стечение обстоятельств, не так ли?
Впрочем, иногда полагаемся. Порой легко спутать счастливый случай с целенаправленным планированием. Рассмотрим несколько примеров.
Случайная реализация
Случайная реализация – это то, что происходит просто потому, что программа написана именно так, как она написана. Вы перестаете полагаться на недокументированную ошибку или граничные условия.
Предположим, что вы вызываете подпрограмму с неверными данными. Подпрограмма откликается определенным образом, и ваша программа основывается на этом отклике. Но у автора даже и в мыслях не было, что программа будет работать подобным образом, – это даже не рассматривалось. Если подпрограмма «исправляется», то основная программа может нарушиться. В самом крайнем случае вызываемая подпрограмма даже не предназначена для того, чего вы от нее ждете, но вроде бы она работает нормально. Вызов каких-либо элементов неправильным образом или в неверном контексте является связанной проблемой.
paint(g);
invalidate();
validate();
revalidate();
repaint();
paintImmediately(r);
Похоже, что Фред предпринимает отчаянные попытки вывести что-то на экран. Но эти подпрограммы не предназначены для того, чтобы к ним обращались таким способом; хотя они кажутся работающими, в действительности это лишь стечение обстоятельств.
Чтобы не получить новых ударов, когда компонент все-таки нарисован, Фред не пытается вернуться назад и устранить поддельные запросы. "Сейчас она работает, оставим все как есть…".
Подобные размышления могут ввести вас в заблуждение. Зачем рисковать, портить то, что работает? Так можно думать по нескольким причинам:
• Программа действительно может не работать, она может лишь казаться работающей.
• Граничное условие, на которое вы полагаетесь, может быть лишь частным случаем. В различных обстоятельствах (например, при ином экранном разрешении) программа может вести себя по-разному.
• Недокументированное поведение может измениться с выпуском новой версии библиотеки.
• Дополнительные и необязательные вызовы замедляют работу программы.
• Дополнительные вызовы также увеличивают риск привнесения новых дефектов, связанных с этим вызовами.
При написании программы, вызываемой другими разработчиками, полезными могут оказаться базовые принципы четкой модуляризации и скрытия реализации за несложными, четко документированными интерфейсами. Четко определенный контракт (см. "Проектирование по контракту") может устранить недоразумения.
Для вызываемых вами подпрограмм полагайтесь только на документированное поведение. Если по какой-то причине вы не можете сделать этого, то четко документируйте ваше предположение.
Случайный контекст
Вы также можете встретиться со "случайным контекстом". Предположим, вы пишете сервисный модуль. Поскольку в данное время вы пишете программу для графической среды, должен ли модуль полагаться на существующий графический интерфейс? Полагаетесь ли вы на англоязычных пользователей? На грамотных пользователей? Полагаетесь ли вы еще на какой-то контекст, наличие которого не гарантируется?
Неявные предположения
Совпадения могут вводить в заблуждение на всех уровнях – от генерации требований до тестирования. Тестирование особенно чревато наличием ложных причинных связей и случайным совпадением результатов. Легко предположить, что А вызывает У, но, как сказано в разделе «Отладка» не предполагайте это, а доказывайте.
На всех уровнях люди работают, держа многие предположения в голове, но они редко документируются и часто вызывают противоречия между разработчиками. Предположения, не основанные на известных фактах, способны отравить любые проекты.
Подсказка 44: Не пишите программы в расчете на стечение обстоятельств