8 ловушек программирования (с Хабра)
Эта статья содержит те ловушки программирования, в которые я попадал сам, продолжаю попадать и возможно никогда не прекращу, а также те, в которых я находил своих товарищей.
Cтатья 1-в-1 скопирована с habrahabr. Захотел себе копию в блог.
Однако я верю в то, что их можно избежать, если знать в какие ловушки можно попасть и как из них выбираться. Возможно эта вера — очередная ловушка.
Краткое введение
Представьте, что вы попали в яму. Пусть она будет глубиной 3 метра. Вы начнете из нее выбираться, карабкаться, цепляться, не знаю, что вы там еще придумаете, но скорее всего вы выберетесь.
В программировании вы также можете попасть в ловушку. Но если мы возьмем яму как аналогию, то глубиной она будет метров 50, а сверху прикрыта тяжеленной стальной пластиной.
Как думаете, долго вы пробудете в такой яме?
Скорее всего до тех пор, пока вас кто-нибудь там не найдет!
Но это не такая уж большая проблема, из каждой ловушки программирования можно выбраться своими силами и почти без посторонних усилий — нужно всего-то знать три вещи: в какой вы ловушке, как вы туда попали и где “потайная дверь” — выход.
Есть одна удивительная вещь в ловушках программирования — почти каждая ловушка это приверженность одной из двух противоположных крайностей, а выбраться из ловушки — значит умело балансировать между ними.
Можно сказать, что избегание ловушек — это ходьба узкой дорожке, где слева — проблемы, а справа — пути их решения, доведенные до абсурда.
Ладно, все равно введение никто не читает, так что поехали!
Ловушка №1: Оптимизация
Как и писал Кнут, преждевременная оптимизация — корень всех зол. Многие знают это правило, некоторые ему не следуют, некоторые возводят его в абсолют, но немногие знают, что запоздалая оптимизация еще хуже!
Слишком большое внимание оптимизации
Часто этим грешит такой тип программистов как “Олимпиадник”. Знание эффективных алгоритмов или внутреннего устройства процессора заставляет их думать, что код должен быть максимально эффективен.
На практике на оптимизацию тратится слишком много времени, код становится совершенно неподдерживаемым, нерасширяемым и неустойчивым к ошибкам.
Признаки того, что вы в ловушке
• Попытка сразу писать быстрый/нетребовательный к ресурсам код
• Желание выжать из кода все, в ущерб остальным показателям
• Вера в то, что O(n) вместо O(n^{2}) или замена всех операций умножения на 2 на побитовый сдвиг сделают из программы конфетку
Слишком маленькое внимание оптимизации
Один из еще незаконченных моих проектов подходит к концу. Стараясь избежать ошибки преждевременной оптимизации, оптимизация была забыта до окончания — перенесена на последнюю (предпоследнюю если уж совсем быть честным) стадию.
Сейчас, когда готово уже более половины я понял — пытаясь избежать одной ловушки я попал в противоположную!
Теперь для того, чтобы улучшить производительность до приемлемого уровня, нужно не просто прогнать пару раз профайлер и исправить пару изолированных в функции алгоритмов, нужно переписать как минимум треть уже написанного кода!
Эта ловушка называется — запоздалая оптимизация, если вы не предусматриваете возможность оптимизации заранее, будьте готовы к тому, что значительную часть вашего красивого и понятного кода придется выкинуть!
Признаки того, что вы в ловушке
• Искренняя вера в то, что любая оптимизация — преждевременная
• Откладывание оптимизации на заключительную часть проекта
• Отказ предусмотреть некоторые небольшие архитектурные решения для будущей оптимизации
Выход
• Не пытайтесь оптимизировать код сразу — создавайте возможности для будущей оптимизации
• Не откладывайте оптимизацию без причины
• Правило 20/80: тратьте свое время на те 20% кода, что дадут 80% результата (да, используйте профилировщик)
Ловушка №2: Абстракция
Абстракция в программировании пожалуй самая хорошая штука. Сначала были двоичные коды, затем мнемоники команд процессора, затем императивное программирование — состояние и операции (утверждения), далее структурное, процедурное, модульное, объектно-ориентированное, функциональное программирование.
Представьте себе, что вам все еще нужно использовать goto, чтобы организовать цикл.
Представьте себе, что вам все еще нужно плодить одинаковые функции, отличающиеся лишь типом данных (вместо использования преимуществ динамической типизации и шаблонов/генериков).
Представьте себе, что вы не можете просто так взять и изменить размер коллекции — нужно выделить новый участок памяти, скопировать туда старую коллекцию, добавить новый элемент, освободить не использующуюся память.
Излишняя сложность
Опять начнем с ловушки, где попытка абстрагировать все на свете доводится до абсурда.
Нагромоздить 20 классов, использовать 12 разных паттернов, реализовать свой DSL для парсинга другого DSL, создать кроссплатформенный фреймворк для визуализации циклических графов для создания очередного тетриса — это про “поборников абстракции”.
Признаки того, что вы в ловушке
• Написание универсального фреймворка перед реализацией основной функциональности, причем использоваться будет от силы 30% написанного кода
• Вера в то, что из любой проблемы лучший выход — использовать какой-либо паттерн
• Написание максимально обобщенной, принимающей все на свете и выдающей корректные результаты для любых входных значений функции, вместо специализированной, даже если ее функционал никогда не будет использован
Упрощение до бесконечности
Признаки “поборников абстракции” чаще встречаются у более опытных программистов. Игнорирование абстракций — у новичков. Лучшие опять балансируют где-то между этими крайностями.
Новички часто еще не знают, что за абстракции есть в программировании вообще и в используемом ими языке в частности. Из-за этого они часто пишут невыразительный код — такой, который можно было бы переписать, используя встроенные языковые средства или сторонние библиотеки/фреймворки сделав его короче, понятнее и, чаще всего, эффективнее.
Признаки того, что вы в ловушке
Для этого пункта довольно сложно подобрать общие признаки — все зависит от языка, задачи и кучи других факторов, но все же:
• Игнорирование абстракций, которые могли бы объективно улучшить код (например, итераторов в C++, монад в Haskell (я имею ввиду отказ от написания своей монады, там где это имеет смысл), генераторов в Python, интерфейсов в Java и т.д.)
• При переходе на новый язык программирования — отказ от изучения его абстракций, в виду того, что и без них жилось неплохо (опять же пример — при переходе с C на C++ — отказ от изучения итераторов, при переходе с императивного языка на Haskell — отказ от изучения монад)
• Вера в то, что те возможности языка, которые вы не используете на самом деле лишние (не касается программистов на C++; шутка конечно, но как известно в каждой шутке…)
Выход
• Не используйте абстракции ради абстракций
• Используйте известные/доступные вам абстракции там где им место и не используйте их там, где им места нет
• Изучите абстракции, которые предоставляет ваш язык программирования
• Перед реализацией какой-либо функциональности, подумайте — действительно ли она вам нужна (принцип YAGNI)
• Где возможно без особых потерь обойтись без абстракций — обходитесь без них (принцип KISS)
Ловушка №3: Перфекционизм
Пожалуй самая популярная ловушка среди разработчиков, слишком много знающих, чтобы просто брать и писать код. На эту тему совсем недавно вышла замечательная небольшая статья на хабре, можете найти ссылку на нее в конце статьи.
Стремление к совершенству
По этой теме все было разжевано и до меня, однако я хочу быть последовательным, поэтому также разберу по-косточкам и эту ловушку.
Признаки того, что вы в ловушке
• Вера в то, что главное в коде — красота, читаемость и поддерживаемость
• Непрекращающийся рефакторинг и переписывание даже приемлемого кода
• Поиск недостатков в любом коде
• Вера в то, что возможно написать идеальный код за разумное время
• Повышенное внимание к мелочам
• Попытки достичь максимально возможного результата
• Длительное обдумывание архитектуры даже простейших частей проекта
Путь хаоса
Из предыдущего списка видно, что перфекционизм — опасная ловушка, может отказаться от него совсем?
Возможно полное игнорирование чистоты кода, отказ от рефакторинга и отключение критического мышления и будут лучше абсолютного перфекционизма (все-таки плохой результат — это результат, а абсолютный перфекционист только выкидывает написанный код), но как и с остальными ловушками, тут важен баланс.
Поэтому полный отказ от своего стремления к совершенству загонит вас в полностью противоположную ловушку.
Признаки того, что вы в ловушке
• Вера в то, что читаемость и расширяемость кода не важна
• Отсутствие критического мышления, особенно по отношению к своему коду
• Довольствование даже минимально приемлемым результатом, или неприемлемым вовсе
• Полный отказ от рефакторинга
• Отказ от обдумывания сложных частей архитектуры
• Непринятие возможности переписать проект или его часть с нуля
Выход
Как всегда где-то посередине:
• Примите, что важна и красота кода и достижение результата, порой второе даже важнее
• Считайте, что переписывание с нуля — крайняя мера, но смиритесь с тем, что это может быть необходимо
• Выделите себе максимальное время, которое вы можете тратить на рефакторинг. Это может быть 20, 50, даже 80% от всего рабочего времени, главное — никогда его не превышайте, чтобы не застрять
• Когда вы обнаруживаете недостаток — подумайте, настолько ли он силен, чтобы тратить время на его исправление
• Используйте правило 20/80 — старайтесь в первую очередь делать те 20% работы, которые принесут 80% результата
Надеюсь эти советы помогут выбраться из ловушки перфекционизма хабровчанам. Все они были поняты на горьком опыте борьбы с этим недугом и буквально выстраданы за все то время, что я занимаюсь программированием.
Ловушка №4: Технологии и инструменты
Программирование не стоит на месте и сейчас в вашем распоряжении тысяча и один инструмент и библиотека, которые если и не сделают за вас всю работу, то уж точно облегчат ее.
Однако иногда они используются либо слишком часто, либо слишком редко. Начнем опять же с первого случая.
Лень
Конечно использование уже созданных библиотек, фреймворков и инструментов — всего-лишь разумное проявление лени, но иногда даже с благими намерениями это заходит слишком далеко.
Подключение одновременно boost, Qt и активное использование STL при написании Hello World — не лучшая идея, но иногда похожие вещи случаются и это — очередная ловушка.
Признаки того, что вы в ловушке
• Подключение библиотеки на 30+ мегабайт, ради Вихря Мерсена
• Вера в то, что все, что нужно уже было написано
• Непринятие того, что велосипед может оказаться лучше
• Невозможность написать и строчки кода без автодополнения в IDE
• Активное использование инструментов, без понимания принципов их работы. Например, частое использование инструментов для визуального редактирования GUI (вроде Qt Creator, CodeGear Rad Studio, MS Visual Studio) и, при этом, отказ от попытки разобраться в их устройстве
Трудоголизм
Значит ли это, что я призываю всех отказываться от использования всех инструментов и библиотек, призываю писать голый код со своими велосипедами в nano (или еще хуже — на папирусе)?
Отнюдь. Наоборот, как вы увидите, полное игнорирование нашего программистского наследования — еще худший порок. И наверное самая опасная ловушка для программистов.
Признаки того, что вы в ловушке
• Регулярное написание все новых и новых велосипедов, без веских причин для этого
• Вера в то, что возможностей языка и стандартной библиотеки должно хватать для всего
• Вера в то, что автодополнение (не как в Sublime Text, а как в IDE) — для склеротиков
• Написание своей билиотеки, пусть и монструозной, но даже не приближающейся по функциональности к существующим аналогам
• Заявления о том, что отладчики и профилировщики нужны тем, кто не понимает как работает собственный код
Выход
• Познакомьтесь с доступными вам инструментами, но не пытайтесь применять их где попало
• Тщательно взвесьте все за и против, перед тем как выбирать между использованием сторонней библиотеки и написанием велосипеда
• Старайтесь улучшить свою продуктивность используя современные инструменты, в тоже время не зависьте от них
• Примите то, что в учебных/тестовых проектах можно делать исключения из этих правил
Ловушка №5: Золотой молоток
Еще один пункт в нашем хит-параде ловушек — золотой молоток.
Золотой молоток — это вредная привычка. После того, как какой-либо метод сработал при решении нескольких задач, попавшийся в ловушку начинает использовать этот метод везде, где только можно!
Есть несколько схожих терминов — панацея, волшебная таблетка, серебряная пуля, т.е. такой метод, который всегда, в 100% случаев выполняет любую задачу. Еще одна имеющая к этой теме поговорка — забивать гвозди микроскопом.
Золотой молоток — довольно часто встречающаяся, но совершенно непохожая на другие ловушка. Золотым молотком может быть все что угодно, кто-то может влюбиться в ООП и писать классы на каждый чих, кто-то в паттерны проектирования и построить программу из одних лишь фабрик и синглтонов.
Для кого-то золотой молоток — это любимый язык программирования, для кого-то — любимая парадигма, для кого-то — методология разработки.
Главное, что выделяет золотой молоток — попытка использовать его всегда и везде.
Признаки того, что вы в ловушке
• Использование одного языка программирования для всех задач
• Вера в то, что одна парадигма может решить все проблемы программирования (чаще всего так говорят про ООП и ФП)
• Использование для всех проектов, независимо от условий одной методологии разработки, например, бездумное использование TDD во всех проектах
• Применение какого-либо средства, предоставляемого языком для большей части задач, например:
– Передача всех аргументов только по константной ссылке
– Использование во всей программе лишь одного типа коллекций, скажем массивов (векторов)
– Использование повсюду немутабельных объектов
– Использование в похожих по синтаксису языках (например, Java и C)
одного стиля отступов и метода наименования сущностей
Серебряной пули не существует
Я боюсь, что эта ловушка — исключение из правил и возможно ее избежать не рискуя попасть в другую.
Это исключение из замеченной мною закономерности еще раз доказывает, что золотого молотка не существует — мой метод описания ловушки как двойственной сущности дал осечку, но я не боюсь отказаться от его использования.
Это и есть правильный путь освобождения из этой ловушки.
Выход
• Если вы знаете лишь один язык программирования — выучите еще один, или два (можно и больше, но в разумных пределах)
• Если вы знаете несколько языков программирования, но у вас есть очевидный любимчик, попробуйте чаще использовать остальные языки
• Если вы программировали только придерживаясь императивного или объектно-ориентированного программирования, попробуйте функциональное
• Если вы постоянно используете какую-либо одну методологию разработки, попробуйте что-нибудь новое
• Перед тем как принимать решения по инерции (и применять свой золотой молоток) — подумайте о возможных альтернативных решениях
Ловушка №6: Кроссплатформенность
Кроссплатформенные приложения — те, что запускаются на нескольких ОС и/или на нескольких платформах.
И тут опять есть две крайности — некоторые разработчики пытаются написать приложение так, чтобы оно работало на всех возможных ОС и одинаково хорошо подходило и для ПК и для планшета и для смартфона.
Для всех и каждого
Часто люди хотят достичь просто невероятной степени кроссплатформенности.
В итоге у них получается, что ни на одной ОС приложение не работает полностью корректно и его одинаково неудобно использовать и на ПК и на планшете и на смартфоне!
Они попали в ловушку излишней кроссплатформенности!
Признаки того, что вы в ловушке
• Вера в то, что можно написать такой код, который выдавал бы приемлемые результаты на всех целевых платформах, без изменений
• Попытки охватить как можно больше операционных систем и платформ, при этом не желая заниматься портированием и изменить хотя-бы часть кода
• Неприязнь к любому коду, предназначенному только для одной платформы
Существует только Win32
И наоборот — некоторые программисты пишут софт, который запустится только на той же ОС, что и у автора, более того, нужна точно такая же мышь, клавиатура и шлем виртуальной реальности.
Часто причина этого, просто то, что программист не задумывается о том, что существуют другие ОС и платформы, помимо тех, что использует он.
Признаки того, что вы в ловушке
• Переписывание всего (или большей части) кода приложения для каждой целевой ОС/платформы
• Написание заведомо труднопортируемого кода там, где этого можно избежать
• Использование нестандартных расширений компилятора/интерпретатора
Выход
• Осторожно определите целевые ОС/платформы
• Будьте готовы к тому, что для некоторых ОС/платформ придется изменить часть кода или даже написать отдельную версию с нуля
• Не привязывайте код к одной платформе специально
• Старайтесь охватить несколько платформ, если это усложнит разработку
Ловушка №7: Защита
(Без)защитное программирование — еще одна замечательная ловушка, в которой вы возможно увидите себя (настоящего или прошлого).
Беззащитное программирование
Беззащитное программирование — противоположность практики защитного программирования, это вера в то, что функции всегда будут переданы правильные аргументы, что побочных эффектов не существует, или они не повлияют на работу кода, что указатель никогда не будет равен null и тому подобные штуки.
Иногда это хорошо, это делает код свободным от множества проверок, однако отладка такого кода — сущий ад. Именно поэтому это ловушка.
Признаки того, что вы в ловушке
• Бесстрашность перед переполнением, делением на ноль и ошибками округления
• Вера в абсолютную непогрешимость стандартных и библиотечных функций/классов
• Вера в то, что пользователь не допустит ошибку при вводе
• Вера в то, что память никогда не закончится
• Вера в то, что все необходимые приложению файлы конфигурации существуют и к ним всегда есть доступ
Защитное программирование
Миллионы тестов для заведомо работающих частей приложения, 15 ASSERT’ов внутри каждой функции, собственная библиотека исключений, логирования, попытки уронить приложение при малейших отклонениях.
Это обратная сторона медали — ловушка защитного программирования.
Признаки того, что вы в ловушке
• Каждая функция в проекте начинается с пачки ассертов или возбуждения исключений
• Тестами покрывается абсолютно весь код, включая сторонние библиотеки
• В проекте имеется собственный класс MyProjectException и сложная иерархия как минимум 10 его наследников, смысл которых сводится к сообщению о неверных аргументах функции
• Запись в лог большей части всего того, что происходит с приложением
• Даже небольшие отклонения для вас неприемлемы и должны ронять приложение с сообщением об ошибке и автоматической отправке баг-репорта с полным дампом памяти
Выход
• Тщательно обдумывайте что стоит проверять, а что нет
• Особое внимание при проверках уделяйте пользовательскому вводу и внешним ресурсам
• По-возможности старайтесь использовать стандартные классы исключений
• Не пытайтесь проверить все
• Отделяйте критические ошибки от незначетельных, позвольте приложению работать дальше, если ничего особо страшного не произошло
• Записывайте в лог только самые важные данные, реализуйте ненавязчивую возможность отправить баг-репорт
Ловушка №8: Откладывание на потом
Ловушка довольно интересная, потому-что найти в этом вопросе баланс очень сложно.
Суть ловушки в том, что часто реализуя какую-либо функциональность, мы ставим пометки — TODO, HACK, и некоторые другие.
Реализация этих задач самому себе откладывается, про TODOшки редко кто вспоминает и они долгое время так и остаются недоделанными.
Признаки того, что вы в ловушке
• Множество пометок TODO и HACK в проекте и их количество не уменьшается
• Code Review не приносит никаких результатов, кроме расстановки новых TODO и WTF
• Невозможность ненадолго отвлечься от выполняемой задачи, чтобы реализовать косвенно-связанную с ней (вместо оставления TODO) или исправить обнаруженную ошибку (вместо оставления FIX или HACK)
Многозадачные программисты
Существуют программисты, которые не могут переключиться в процессе реализации какой-либо части проекта на другую, пусть даже непосредственно с ней связанную (скажем написать класс исключения и выбросить его, вместо оставления пометки “TODO: check argument to null”).
И наоборот, существуют программисты, которые это умеют, я называю таких многозадачными программистами, но вот беда — реальной многозадачности нет и на самом деле они просто быстро переключаются с одной задачи на другую.
У этого есть свой плюс — TODO и HACK в коде не ставятся, а сразу правятся, найденные ошибки в другом коде — устраняются.
И как обычно, бесплатно это не дается, как итог из-за слишком частого переключения программист может забыть о первоначальной задаче или не успеть ее доделать, а когда вернется к ней снова — не вспомнить в каком направлении двигался.
Если вам нравится быстро и часто переключаться между различными частами проекта, у меня для вас плохие новости.
Признаки того, что вы в ловушке
• Вы не оставляете ни одного TODO на потом, чтобы сконцентрироваться на задаче
• Вы переключаетесь так часто, что начав реализовывать один функционал, реализуете вместо него две совершенно несвязанные фичи
• Когда вы программируете в паре, партнер не может уследить за ходом ваших мыслей
Выход
• Перед тем как отложить задачу и поставить TODO подумайте, может реализовать ее можно быстро
• Не бойтесь отвлекаться от основной задачи, но не делайте это слишком часто и на долго
• Осматривая код, исправляйте найденные ошибки сразу
• Откладывайте несвязанный с текущей задачей большой функционал — оставляйте TODO
• Программируя в паре, избегайте переключений вообще, откладывайте все посторонние задачи
Ссылки
• AlenaC++: Кому на самом деле принадлежит цитата о преждевременной оптимизации
• WordPresse: Защитное программирование
• Habrahabr: Запоздалая оптимизация
• Habrahabr: 5 стадий некомпетентности программиста
• Habrahabr: Эти бесчисленные парадигмы, концепции, инструменты и фреймворки
• Habrahabr: Паралич анализа: вы знаете слишком много, чтобы просто писать код
• Habrahabr: Три ключевых принципа ПО, которые вы должны понимать
• Habrahabr: Кроссплатформенность — это круто
• Habrahabr: Аппаратная кроссплатформенность: есть ли выход?
• Habrahabr: Защитное программирование
• Wikipedia: KISS
• Wikipedia: YAGNY
• Wikipedia: DRY
• Wikipedia: Перфекционизм
• Wikipedia: Золотой молоток
• Wikipedia: Прокрастинация