Ответ спасен из Яндекс.Кью
Я пишу код уже больше 20 лет и, хотя в последнее время больше занимаюсь руководством, на пике формы был способен писать по 500+ строк хорошо работающего кода в день. Вот принципы, которые мне в этом помогали:
-
Не переобобщайте. Если не получается малой кровью создать универсальное решение, то и неважно, решите конкретную текущую задачу и двигайтесь дальше. Обобщение, даже хорошее, в 70% случаев так и остается нигде больше не использованным.
-
Не оптимизируйте код заранее. Идея усложнить код ради его ускорения почти всегда ошибочна. Исключение возможно только в том случае, когда именно этот участок код "тормозит" так, что это уже заметно на уровне продукта или бизнеса. "Пессимизировать" код тоже, конечно, не нужно, из двух версий, одинаковых по сложности и по объему кода, выбирайте более быструю. Из этого есть важное следствие: нельзя дублировать данные и нельзя кешировать результаты вычислений там, где этого не требует во весь голос производительность. Больше половины структурных багов возникает из-за того, что "разъехались" кэш и реальные данные, причем еще и отлаживать такое обычно адски сложно, потому что в момент собственно "разъезжания" никакого бага еще не видно, он проявится потом, когда ставить breakpoint-ы и проходить исполнение по шагам уже поздно.
-
Называйте и группируйте всё происходящее правильно. Код, в котором нет алгоритмических или технологических сложностей, должен читаться как текст, написанный по-английски. Хорошо, когда код, в котором ниндзя куда-то крадётся, выглядит как-то вроде ninja.sneak(...), а не pDst2.trySetCoord(...) и ещё десять строчек после этой, ни одну из которых нельзя забыть. Если функция что-то меняет в состоянии объекта, она не может называться isSomething -- если так сделать, следующий же код с её участием обречён на интересный дебаг. Если функция что-то трудно вычисляет, она не может называться getSomething -- кто-нибудь наверняка начнёт вызывать её в цикле и удивляться, почему всё тормозит. Класс, который хранит состояние документа, может называться DocumentState или Document, но никак не SDManager. Кстати, про Manager-ов. Если единственное название, которое вы можете выбрать для класса или метода, получается очень расплывчатым, это верный признак того, что вы делаете что-то неправильно. Классы BaseObject и World или функции databaseOps и initService быстро приведут к самым разным проблемам и багам, связанным с нарушениями этого и предыдущего пунктов.
-
Не смешивайте алгоритмы и другие технологически сложные участки кода с бизнес-логикой. Выразительности современных языков программирования вполне достаточно для того, чтобы, скажем, графический движок компьютерной игры ничего не знал о ниндзя и вертолётах, функции работы с БД в CRM-системе не знали слов "счёт" и "клиент", и т.д. и т.п. Для бизнес-логики типичны постоянные изменения, нечеткость и путаница. Как только сущности с разных уровней абстракции начинают упоминаться в соседних строчках кода, , всё это тут же начинает проникать и в технологически сложный код, и всё взрывается.
-
Не используйте никакие advanced фичи никакого языка. В С++, например, не стоит пользоваться темплейтной магией, переопределением операторов, множественным наследованием и т.д. и т.п. Экзотические языки программирования (Haskell, диалекты Лиспа, хитрые декларативные язычки, работающие поверх JVM) вообще стоит использовать только как хобби, источник вдохновения. Не напрямую в той работе, за которую вам деньги платят. Эта точка зрения часто вызывает споры. К сожалению, обстоятельно аргументировать её в формате ответа на Знатоках не получится. Поэтому просто сошлюсь на свой почти 20-летний опыт индустриального программирования. Во всех областях и организациях, в которых я успел поработать, что в Яндексе, что в разработке игр, что в науке идея использовать в качестве рабочего инструмента "красивый полёт свободной мысли, недоступный простым умам" оказывалась разрушительной. Часто и для всего проекта, но всегда, без исключений, для автора идеи.
-
Стоит выкинуть из головы все ООП. Единственное полезное, что в императивные языки пришло из этой идеологии - модификаторы private. Иерархии классов это зло, наследовать реализации нужно себе запретить. Наследовать можно интерфейсы, и то не слишком много уровней. Агрегация почти всегда лучше наследования. Большая часть классических "шаблонов проектирования" уже либо устарела, либо нашла поддержку на уровне языка.
-
Используйте как можно больше assert'ов, логов и прочих способов поймать незапланированное состояние системы как можно раньше. Очень часто в момент, когда неверное поведение системы становится заметно пользователю, дебажить её уже сложно. Если же вы смогли поймать систему именно в тот момент, когда её внутреннее состояние впервые становится неконсистентным или она начинает вести себя не так, как вы задумывали, чаще всего разобраться в том, почему, становится тривиально.
-
Каждая лишняя строчка кода это зло. Там, где это вообще возможно, не стоит пользоваться чужим кодом, который вы не прочитали и не поняли от и до. Это касается в том числе и широко известных библиотек и фреймворков общего назначения. Чем меньше кода (включая и тот, который пишешь сам, и тот, от которого зависишь) - тем лучше.
-
Граничные случаи стоит проверять "в голове" прямо по ходу написания кода. Например: я пишу list.back(), а почему этот список не пуст? Как я "доказал" к этому моменту, что этого не может произойти? Что сделает эта функция, работающая со строчкой, если она пуста?
-
Любой баг, если он все-таки вам встретился, старайтесь возводить до первопричины и до общего правила. Что я написал в коде такого, что этот баг вообще оказался возможен? Как я могу поменять свои практики так, чтобы больше никогда не допускать таких же? Например, баг состоял в том, что я написал такую-то строчку в функции save и забыл добавить симметричную в функции load. Может быть, пора, наконец, заменить эту пару на одну функцию serialize? Обложить их тестами? Или хотя бы поклясться вслух самому себе, что никогда не будете трогать их по одиночке? Или, например, причина бага была в том, что в указателе pNeighbor содержится null, а программа этого не ожидает и падает. Можно просто воткнуть if (pNeighbor != null) и закрыть баг как исправленный. А где ещё в коде разыменовывается pNeighbor? Везде ли есть такая же проверка? Насколько вообще эта ситуация легальна, может быть, настоящая ошибка там, где pNeighbor впервые оказался нулевым? Если значение pNeighbor это результат отображения NULL из БД на объектную модель, то как этот NULL попал в БД, кто его туда положил и не стоит ли воткнуть там constraint? И т.д и т.п. Никогда не считайте, что ваш код уже идеален! Наблюдайте за собой, совершенствуйтесь, старайтесь работать вместе с людьми, у которых есть, чему поучиться.
Тема эта неисчерпаема, приёмов и приёмчиков можно вспомнить ещё много, но я, пожалуй, остановлюсь на этой десятке. Всем хорошего кода!