Skip to content

[G]eneral

Serhii Chepets edited this page Apr 13, 2023 · 36 revisions

“Most programs today are like Egyptian pyramids with a million bricks stacked on top of each other and without structural integrity—they are simply built by brute force and thousands of slaves.”

― Alan Kay


[G01]: Multiple Languages in One Source File

Today’s modern programming environments make it possible to put many different languages into a single source file. For example, a Java source file might contain snippets of XML, HTML, YAML, JavaDoc, English, JavaScript, and so on. For another example, in addition to HTML a JSP file might contain Java, a tag library syntax, English comments, Javadocs, XML, JavaScript, and so forth. This is confusing at best and carelessly sloppy at worst.The ideal is for a source file to contain one, and only one, language. Realistically, we will probably have to use more than one. But we should take pains to minimize both the number and extent of extra languages in our source files.


[G01]: Несколько языков в одном исходном файле

Современные среды программирования позволяют объединять в одном исходном файле код, написанный на разных языках. Например, исходный файл на языке Java может содержать вставки XML, HTML, YAML, JavaDoc, English, JavaScript и т. д. Или, скажем, наряду с кодом HTML в файле JSP может присутствовать код Java, синтаксис библиотеки тегов, комментарии на английском языке, комментарии Javadoc, XML, JavaScript и т. д. В лучшем случае результат получается запутанным, а в худшем — неаккуратным и ненадежным.

В идеале исходный файл должен содержать код на одном — и только одном! — языке. На практике без смешения языков обойтись, скорее всего, не удастся. Но по крайней мере следует свести к минимуму как количество, так и объем кода на дополнительных языках в исходных файлах.


[G01]: Декілька мов в одному вихідному файлі

Сучасні середовища програмування дозволяють об'єднувати в одному вихідному файлі код, написаний різними мовами. Наприклад, вихідний файл на мові Java може містити вставки XML, HTML, YAML, JavaDoc, English, JavaScript тощо. Або, скажімо, поряд з кодом HTML у файлі JSP може бути код Java, синтаксис бібліотеки тегів, коментарі англійською мовою , коментарі Javadoc, XML, JavaScript і т. д. У кращому випадку результат виходить заплутаним, а в гіршому – неакуратним та ненадійним.

В ідеалі вихідний файл повинен містити код на одному – і лише на одному! - Мовою. Насправді без змішання мов обійтися, швидше за все, не вдасться. Але принаймні слід звести до мінімуму як кількість, так і обсяг коду додатковими мовами у вихідних файлах.


[G02]: Obvious Behavior Is Unimplemented

Following "The Principle of Least Surprise", any function or class should implement the behaviors that another programmer could reasonably expect. When an obvious behavior is not implemented, readers and users of the code can no longer depend on their intuition about function names. They lose their trust in the original author and must fall back on reading the details of the code.

Example: C# | Java


[G02]: Очевидное поведение не реализовано

Согласно "принципу наименьшего удивления", любая функция или класс должны реализовать то поведение, которого от них вправе ожидать программист. Если очевидное поведение не реализовано, читатели и пользователи кода перестают полагаться на свою интуицию в отношении имен функций. Они теряют доверие к автору кода и им приходится разбираться во всех подробностях реализации.

Пример: C# | Java


[G02]: Очевидна поведінка не реалізована

Згідно з "принципом найменшого подиву", будь-яка функція або клас повинні реалізувати ту поведінку, на яку від них вправі чекати програміст. Якщо очевидна поведінка не реалізована, читачі та користувачі коду перестають покладатися на свою інтуїцію щодо імен функцій. Вони втрачають довіру до автора коду і їм доводиться розумітися на всіх подробицях реалізації.

Приклад: C# | Java


[G03]: Incorrect Behavior at the Boundaries

It seems obvious to say that code should behave correctly. The problem is that we seldom realize just how complicated correct behavior is. Developers often write functions that they think will work, and then trust their intuition rather than going to the effort to prove that their code works in all the corner and boundary cases.

There is no replacement for due diligence. Every boundary condition, every corner case, every quirk and exception represents something that can confound an elegant and intuitive algorithm. Don’t rely on your intuition. Look for every boundary condition and write a test for it.


[G03]: Некорректное граничное поведение

Код должен работать правильно — вроде бы очевидное утверждение. Беда в том, что мы редко понимаем, насколько сложным бывает правильное поведение. Разработчики часто пишут функции, которые в их представлении работают, а затем доверяются своей интуиции вместо того, чтобы тщательно проверить работоспособность своего кода во всех граничных и особых ситуациях.

Усердие и терпение ничем не заменить. Каждая граничная ситуация, каждый необычный и особый случай способны нарушить работу элегантного и интуитивного алгоритма. Не полагайтесь на свою интуицию. Найдите каждое граничное условие и напишите для него тест.


[G03]: Некоректна гранична поведінка

Код має працювати правильно — начебто очевидне твердження. Погано те, що ми рідко розуміємо, наскільки складною буває правильна поведінка. Розробники часто пишуть функції, які у їхньому поданні працюють, а потім довіряються своїй інтуїції замість того, щоб ретельно перевірити працездатність свого коду у всіх граничних та особливих ситуаціях.

Старанність та терпіння нічим не замінити. Кожна гранична ситуація, кожен незвичайний та особливий випадок здатні порушити роботу елегантного та інтуїтивного алгоритму. Не покладайтеся на свою інтуїцію. Знайдіть кожну граничну умову та напишіть для нього тест.


[G04]: Overridden Safeties

It is risky to override safeties. This may be necessary, but it is always risky. Turning off certain compiler warnings (or all warnings!) may help you get the build to succeed, but at the risk of endless debugging sessions. Turning off failing tests and telling yourself you’ll get them to pass later is as bad as pretending your credit cards are free money.

Example: C# | Java


[G04]: Отключенные средства безопасности

Отключать средства безопасности рискованно. Это бывает необходимо, но оно всегда сопряжено с риском. Иногда отключение некоторых (или всех!) предупреждений компилятора позволяет успешно построить программу, но при этом вы рискуете бесконечными отладочными сеансами. Не отключайте сбойные тесты, обещая себе, что вы заставите их проходить позднее, — это так же неразумно, как считать кредитную карту источником бесплатных денег.

Пример: C# | Java


[G04]: Відключені засоби безпеки

Вимикати засоби безпеки ризиковано. Це буває необхідним, але воно завжди пов'язане з ризиком. Іноді вимкнення деяких (або всіх!) попереджень компілятора дозволяє успішно побудувати програму, але при цьому ви ризикуєте нескінченними сеансами налагодження. Не відключайте збійні тести, обіцяючи собі, що ви змусите їх проходити пізніше, це так само нерозумно, як вважати кредитну картку джерелом безкоштовних грошей.

Приклад: C# | Java


[G05]: Duplication

Every time you see duplication in the code, it represents a missed opportunity for abstraction. That duplication could probably become a subroutine or perhaps another class outright. By folding the duplication into such an abstraction, you increase the vocabulary of the language of your design. Other programmers can use the abstract facilities you create. Coding becomes faster and less error prone because you have raised the abstraction level.

The most obvious form of duplication is when you have clumps of identical code that look like some programmers went wild with the mouse, pasting the same code over and over again. These should be replaced with simple methods.

A more subtle form is the switch/case or if/else chain that appears again and again in various modules, always testing for the same set of conditions. These should be replaced with polymorphism.

Still more subtle are the modules that have similar algorithms, but that don’t share similar lines of code. This is still duplication and should be addressed by using the TEMPLATE METHOD, or STRATEGY pattern.

Indeed, most of the design patterns that have appeared in the last fifteen years are simply well-known ways to eliminate duplication. So too the Codd Normal Forms are a strategy for eliminating duplication in database schemae. OO itself is a strategy for organizing modules and eliminating duplication. Not surprisingly, so is structured programming.

Find and eliminate duplication wherever you can.

Example: C# | Java


[G05]: Дублирование

Каждый раз, когда в программе встречается повторяющийся код, он указывает на упущенную возможность для абстракции. Возможно, дубликат мог бы стать функцией или даже отдельным классом. «Сворачивая» дублирование в подобные абстракции, вы расширяете лексикон языка программирования. Другие программисты могут воспользоваться созданными вами абстрактными концепциями. Повышение уровня абстракции ускоряет программирование и снижает вероятность ошибок.

Простейшая форма дублирования — куски одинакового кода. Программа выглядит так, словно у программиста дрожат руки, и он снова и снова вставляет один и тот же фрагмент. Такие дубликаты заменяются простыми методами.

Менее тривиальная форма дублирования — цепочки switch/case или if/else, снова и снова встречающиеся в разных модулях и всегда проверяющие одинаковые наборы условий. Вместо них надлежит применять полиморфизм.

Еще сложнее модули со сходными алгоритмами, но содержащие похожих строк кода. Однако дублирование присутствует и в этом случае. Проблема решается применением паттернов ШАБЛОННЫЙ МЕТОД или СТРАТЕГИЯ [GOF].

В сущности, большинство паттернов проектирования, появившихся за последние 15 лет, представляет собой хорошо известные способы борьбы с дублированием. Нормальные формы Кодда устраняют дублирование в схемах баз данных. Само объектно-ориентированное программирование может рассматриваться как стратегия модульной организации кода и устранения дубликатов. Естественно, это относится и к структурному программированию.

Ищите и устраняйте дубликаты повсюду, где это возможно.

Пример: C# | Java


[G05]: Дублювання

Щоразу, коли в програмі зустрічається код, що повторюється, він вказує на втрачену можливість для абстракції. Можливо, дублікат міг би стати функцією чи навіть окремим класом. "Згортаючи" дублювання в подібні абстракції, ви розширюєте лексикон мови програмування. Інші програмісти можуть скористатися створеними вами абстрактними концепціями. Підвищення рівня абстракції прискорює програмування та знижує ймовірність помилок.

Найпростіша форма дублювання - шматки однакового коду. Програма виглядає так, ніби у програміста тремтять руки, і він знову і знову вставляє один і той самий фрагмент. Такі дублікати замінюються простими методами.

Менш тривіальна форма дублювання — ланцюжки switch/case чи if/else, які знову і знову зустрічаються в різних модулях і завжди перевіряють однакові набори умов. Замість них слід застосовувати поліморфізм.

Ще складніші модулі зі схожими алгоритмами, але які містять схожих рядків коду. Однак дублювання є і в цьому випадку. Проблема вирішується застосуванням патернів ШАБЛОННИЙ МЕТОД або СТРАТЕГІЯ [GOF].

По суті, більшість патернів проектування, що з'явилися за останні 15 років, є добре відомими способами боротьби з дублюванням. Нормальні форми Кодда усувають дублювання у схемах баз даних. Саме об'єктно-орієнтоване програмування може розглядатися як стратегія модульної організації коду та усунення дублікатів. Звичайно, це стосується і структурного програмування.

Шукайте та усувайте дублікати всюди, де це можливо.

Приклад: C# | Java


[G06]: Code at Wrong Level of Abstraction

It is important to create abstractions that separate higher level general concepts from lower level detailed concepts. Sometimes we do this by creating abstract classes to hold the higher level concepts and derivatives to hold the lower level concepts. When we do this, we need to make sure that the separation is complete. We want all the lower level concepts to be in the derivatives and all the higher level concepts to be in the base class.

Constants, variables, or utility functions that pertain only to the detailed implementation should not be present in the base class. The base class should know nothing about them.

This rule also pertains to source files, components, and modules. Good software design requires that we separate concepts at different levels and place them in different containers. Sometimes these containers are base classes or derivatives and sometimes they are source files, modules, or components. Whatever the case may be, the separation needs to be complete. We don’t want lower and higher level concepts mixed together.

Example: C# | Java

The point is that you cannot lie or fake your way out of a misplaced abstraction. Isolating abstractions is one of the hardest things that software evelopers do, and there is no quick fix when you get it wrong.


[G06]: Код на неверном уровне абстракции

В программировании важную роль играют абстракции, отделяющие высокоуровневые общие концепции от низкоуровневых подробностей. Иногда эта задача решается созданием абстрактных классов, содержащих высокоуровневые концепции, и производных классов, в которых хранятся низкоуровневые концепции. Действуя подобным образом, необходимо позаботиться о том, чтобы разделение было полным. Все низкоуровневые концепции должны быть сосредоточены в производных классах, а все высокоуровневые концепции объединяются в базовом классе.

Константы, переменные и вспомогательные функции, относящиеся только к конкретной реализации, исключаются из базового класса. Базовый класс не должен ничего знать о них.

Правило также относится к исходным файлам, компонентам и модулям. Качественное проектирование требует, чтобы концепции разделялись на разных уровнях и размещались в разных контейнерах. Иногда такими контейнерами являются базовые и производные классы; в других случаях это могут быть исходные файлы, модули или компоненты. Но какое бы решение ни было выбрано в конкретном случае, разделение должно быть полным. Высокоуровневые и низкоуровневые концепции не должны смешиваться.

Пример: C# | Java

Ложь и фикции не способны компенсировать неверного размещения абстракций. Разделение абстракций — одна из самых сложных задач, решаемых разработчиками. Если выбор сделан неверно, не надейтесь, что вам удастся найти простое обходное решение.


[G06]: Код на неправильному рівні абстракції

У програмуванні важливу роль відіграють абстракції, що відокремлюють загальні високорівневі концепції від низькорівневих подробиць. Іноді це завдання вирішується створенням абстрактних класів, що містять високорівневі концепції, та похідних класів, у яких зберігаються низькорівневі концепції. Діючи таким чином, необхідно подбати про те, щоб поділ був повним. Усі низькорівневі концепції мають бути зосереджені у похідних класах, проте високорівневі концепції об'єднуються у базовому класі.

Константи, змінні та допоміжні функції, що стосуються лише конкретної реалізації, виключаються з базового класу. Базовий клас не повинен нічого знати про них.

Правило також відноситься до вихідних файлів, компонентів та модулів. Якісне проектування вимагає, щоб концепції поділялися на різних рівнях та розміщувалися у різних контейнерах. Іноді такими контейнерами є базові та похідні класи; в інших випадках це можуть бути вихідні файли, модулі чи компоненти. Але яке б рішення не було обрано у конкретному випадку, поділ має бути повним. Високорівневі та низькорівневі концепції не повинні змішуватись.

Приклад: C# | Java

Брехня та фікції не здатні компенсувати невірного розміщення абстракцій. Поділ абстракцій — одне з найскладніших завдань, які розв'язують розробники. Якщо вибір зроблено неправильно, не сподівайтеся, що вам вдасться знайти просте обхідне рішення.


[G07]: Base Classes Depending on Their Derivatives

The most common reason for partitioning concepts into base and derivative classes is so that the higher level base class concepts can be independent of the lower level derivative class concepts. Therefore, when we see base classes mentioning the names of their derivatives, we suspect a problem. In general, base classes should know nothing about their derivatives.

Example: C# | Java

Exceptions

There are exceptions to this rule, of course. Sometimes the number of derivatives is strictly fixed, and the base class has code that selects between the derivatives. We see this a lot in finite state machine implementations. However, in that case the derivatives and base class are strongly coupled and always deploy together in the same jar file. In the general case we want to be able to deploy derivatives and bases in different jar files.

Deploying derivatives and bases in different jar files and making sure the base jar files know nothing about the contents of the derivative jar files allow us to deploy our systems in discrete and independent components. When such components are modified, they can be redeployed without having to redeploy the base components. This means that the impact of a change is greatly lessened, and maintaining systems in the field is made much simpler.


[G07]: Базовые классы, зависящие от производных

Самая распространенная причина для разбиения концепций на базовые и производные классы состоит в том, чтобы концепции базового класса, относящиеся к более высокому уровню, были независимы от низкоуровневых концепций производных классов. Следовательно, когда в базовом классе встречаются упоминания имен производных классов, значит, в проектировании что-то сделано не так. В общем случае базовые классы не должны ничего знать о своих производных классах.

Пример: C# | Java

Исключения

Конечно, у этого правила имеются свои исключения. Иногда количество производных классов жестко фиксировано, а в базовом классе присутствует код для выбора между производными классами. Подобная ситуация часто встречается в реализациях конечных автоматов. Однако в этом случае между базовым и производными классами существует жесткая привязка, и они всегда размещаются вместе в одном файле jar. В общем случае нам хотелось бы иметь возможность размещения производных и базовых классов в разных файлах jar.

Размещение производных и базовых классов в разных файлах jar, при котором базовые файлы jar ничего не знают о содержимом производных файлов jar, позволяет организовать развертывание систем в формате дискретных, независимых компонентов. Если в такие компоненты будут внесены изменения, то они развертываются заново без необходимости повторного развертывания базовых компонентов. Такая архитектура значительно сокращает последствия от вносимых изменений и упрощает сопровождение систем в условиях реальной эксплуатации.


[G07]: Базові класи, що залежать від похідних

Найпоширеніша причина для розбиття концепцій на базові та похідні класи полягає в тому, щоб концепції базового класу, що стосуються до вищого рівня були незалежні від низькорівневих концепцій похідних класів. Отже, як у базовому класі зустрічаються згадки імен похідних класів, отже, у проектуванні щось зроблено негаразд. У загальному випадку базові класи не повинні нічого знати про свої похідні класи.

Приклад: C# | Java

Винятки

Звичайно, це правило має свої винятки. Іноді кількість похідних класів жорстко фіксована, а в базовому класі є код для вибору між похідними класами Подібна ситуація часто зустрічається у реалізаціях кінцевих автоматів. Однак у цьому випадку між базовим і похідними класами існує жорстка прив'язка, і вони розміщуються разом у одному файлі jar. Загалом нам хотілося б мати можливість розміщення похідних та базових класів у різних файлах jar.

Розміщення похідних та базових класів у різних файлах jar, при якому базові файли jar нічого не знають про вміст похідних файлів jar, дозволяє організувати розгортання систем у форматі дискретних, незалежних компонентів. Якщо такі компоненти будуть внесені зміни, вони розгортаються заново без необхідності повторного розгортання базових компонентів. Така архітектура значно скорочує наслідки від внесених змін та спрощує супровід систем в умовах реальної експлуатації.


[G08]: Too Much Information

Well-defined modules have very small interfaces that allow you to do a lot with a little. Poorly defined modules have wide and deep interfaces that force you to use many different gestures to get simple things done. A well-defined interface does not offer very many functions to depend upon, so coupling is low. A poorly defined interface provides lots of functions that you must call, so coupling is high.

Limit expose at the interfaces of their classes and modules. The fewer methods a class has, the better. The fewer variables a function knows about, the better. The fewer instance variables a class has, the better.

Hide your data. Hide your utility functions. Hide your constants and your temporaries. Don’t create classes with lots of methods or lots of instance variables. Don’t create lots of protected variables and functions for your subclasses. Concentrate on keeping interfaces very tight and very small. Help keep coupling low by limiting information.


[G08]: Слишком много информации

Хорошо определенные модули обладают компактными интерфейсами, позволяющими сделать много минимальными средствами. Для плохо определенных модулей характерны широкие, глубокие интерфейсы, которые заставляют пользователя выполнять много разных операций для решения простых задач. Хорошо определенный интерфейс предоставляет относительно небольшое количество функций, поэтому степень логической привязки при его использовании относительно невелика. Плохо определенный интерфейс предоставляет множество функций, которые необходимо вызывать, поэтому его использование сопряжено с высокой степенью логической привязки.

Ограничивайте доступ к интерфейсам своих классов и модулей. Чем меньше методов содержит класс, тем лучше. Чем меньше переменных известно функции, тем лучше. Чем меньше переменных экземпляров содержит класс, тем лучше.

Скрывайте свои данные. Скрывайте вспомогательные функции. Скрывайте константы и временные переменные. Не создавайте классы с большим количеством методов или переменных экземпляров. Не создавайте большого количества защищенных переменных и функций в субклассах. Сосредоточьтесь на создании очень компактных, концентрированных интерфейсов. Сокращайте логические привязки за счет ограничения информации.


[G08]: Занадто багато інформації

Добре певні модулі мають компактні інтерфейси, що дозволяють зробити багато мінімальними засобами. Для погано визначених модулів характерні широкі, глибокі інтерфейси, які змушують користувача виконувати багато різних операцій на вирішення простих завдань. Добре певний інтерфейс надає відносно невелику кількість функцій, тому ступінь логічної прив'язки при використанні відносно невелика. Погано певний інтерфейс надає безліч функцій, які необхідно викликати, тому його використання пов'язане з високим рівнем логічної прив'язки.

Обмежуйте доступ до інтерфейсів своїх класів та модулів. Чим менше методів містить клас, тим краще. Чим менше змінних відоме функції, тим краще. Чим менше змінних екземплярів містить клас, тим краще.

Приховуйте дані. Приховуйте допоміжні функції. Приховуйте константи та тимчасові змінні. Не створюйте класи з великою кількістю методів чи змінних екземплярів. Не створюйте велику кількість захищених змінних та функцій у субкласах. Зосередьтеся на створенні компактних, концентрованих інтерфейсів. Зменшуйте логічні прив'язки за рахунок обмеження інформації.


[G09]: Dead Code

Dead code is code that isn’t executed. You find it in the body of an if statement that checks for a condition that can’t happen. You find it in the catch block of a try that never throws. You find it in little utility methods that are never called or switch/case conditions that never occur.

The problem with dead code is that after awhile it starts to smell. The older it is, the stronger and sourer the odor becomes. This is because dead code is not completely updated when designs change. It still compiles, but it does not follow newer conventions or rules. It was written at a time when the system was different. When you find dead code, do the right thing. Give it a decent burial. Delete it from the system.


[G09]: Мертвый код

Мертвым кодом называется код, не выполняемый в ходе работы программы. Он содержится в теле команды if, проверяющей невозможное условие. Он содержится в секции catch для блока try, никогда не инициирующего исключения. Он содержится в маленьких вспомогательных методах, которые никогда не вызываются, или в никогда не встречающихся условиях switch/case.

Мертвый код плох тем, что спустя некоторое время он начинает «плохо пахнуть». Чем древнее код, тем сильнее и резче запах. Дело в том, что мертвый код не обновляется при изменении архитектуры. Он компилируется, но не соответствует более новым конвенциям и правилам. Он был написан в то время, когда система была другой. Обнаружив мертвый код, сделайте то, что положено делать в таких случаях: достойно похороните его. Удалите его из системы.


[G09]: Мертвий код

Мертвим кодом називається код, який виконується під час роботи програми. Він міститься в тілі команди if, яка перевіряє неможливу умову. Він міститься в секції catch для блоку try, ніколи не ініціюючого виключення. Він міститься в маленьких допоміжних методах, які ніколи не викликаються, або в умовах, що ніколи не зустрічаються switch/case.

Мертвий код поганий тим, що через деякий час він починає «погано пахнути». Чим давніший код, тим сильніший і різкіший запах. Справа в тому, що мертвий код не оновлюється при зміні архітектури. Він компілюється, але не відповідає більш новим конвенціям і правилам. Він був написаний у той час, коли система була іншою. Виявивши мертвий код, зробіть те, що потрібно робити в таких випадках: гідно поховайте його. Видаліть його із системи.


[G10]: Vertical Separation

Variables and function should be defined close to where they are used. Local variables should be declared just above their first usage and should have a small vertical scope. We don’t want local variables declared hundreds of lines distant from their usages.

Private functions should be defined just below their first usage. Private functions belong to the scope of the whole class, but we’d still like to limit the vertical distance between the invocations and definitions. Finding a private function should just be a matter of scanning downward from the first usage.


[G10]: Вертикальное разделение

Переменные и функции должны определяться вблизи от места их использования. Локальные переменные должны объявляться непосредственно перед первым использованием и должны обладать небольшой вертикальной областью видимости. Объявление локальной переменной не должно отдаляться от места ее использования на сотню строк.

Приватные функции должны определяться сразу же после первого использования. Приватные функции принадлежат области видимости всего класса, но вертикальное расстояние между вызовами и определениями все равно должно быть минимальным. Приватная функция должна обнаруживаться простым просмотром кода от места первого использования.


[G10]: Вертикальний поділ

Змінні та функції повинні визначатися поблизу місця їх використання. Локальні змінні мають оголошуватися безпосередньо перед першим використанням і повинні мати невелику вертикальну область видимості. Оголошення локальної змінної не повинно віддалятися від місця використання на сотню рядків.

Приватні функції повинні визначатися відразу після першого використання. Приватні функції належать області видимості всього класу, але вертикальна відстань між викликами та визначеннями все одно має бути мінімальною. Приватна функція повинна бути простим переглядом коду від місця першого використання.


[G11]: Inconsistency

If you do something a certain way, do all similar things in the same way. This goes back to the principle of least surprise. Be careful with the conventions you choose, and once chosen, be careful to continue to follow them.

Example: C# | Java

Simple consistency like this, when reliably applied, can make code much easier to read and modify.


[G11]: Непоследовательность

Если некая операция выполняется определенным образом, то и все похожие операции должны выполняться так же. Это правило возвращает нас к «принципу наименьшего удивления». Ответственно подходите к выбору новых схем и обозначений, а если уж выбрали — продолжайте следовать им.

Пример: C# | Java

Последовательное соблюдение подобных схем и правил существенно упрощает чтение и модификацию кода.


[G11]: Непослідовність

Якщо якась операція виконується певним чином, то всі схожі операції повинні виконуватися так само. Це правило повертає нас до «принципу найменшого подиву». Відповідально підходьте до вибору нових схем та позначень, а якщо вже вибрали - продовжуйте слідувати їм.

Приклад: C# | Java

Послідовне дотримання подібних схем та правил суттєво спрощує читання та модифікацію коду.

Приклад

Якщо в функцію включена змінна response для зберігання даних HttpServletResponse, будьте послідовними та використовуйте таке ж ім'я змінної в інших функціях, що працюють з об'єктами HttpServletResponse. Якщо метод називається processVerificationRequest, надавайте схожі імена (наприклад, processDeletionRequest) методам, що обробляє інші запити.


[G12]: Clutter

Of what use is a default constructor with no implementation? All it serves to do is clutter up the code with meaningless artifacts. Variables that aren’t used, functions that are never called, comments that add no information, and so forth. All these things are clutter and should be removed. Keep your source files clean, well organized, and free of clutter.


[G12]: Балласт

Какой прок от конструктора по умолчанию, не имеющего реализации? Он только попусту загромождает код. Неиспользуемые переменные, невызываемые функции, бессодержательные комментарии — все это бесполезный балласт, который следует удалить. Поддерживайте чистоту в своих исходных файлах, следите за их структурой и не допускайте появления балласта.


[G12]: Баласт

Яка користь від конструктора за замовчуванням, що не має реалізації? Він тільки марно захаращує код. Змінні, що не використовуються, невикликані функції, беззмістовні коментарі — все це марний баласт, який слід видалити. Підтримуйте чистоту у своїх вихідних файлах, стежте за їхньою структурою і не допускайте появи баласту.


[G13]: Artificial Coupling

Things that don’t depend upon each other should not be artificially coupled. For example, general enums should not be contained within more specific classes because this forces the whole application to know about these more specific classes. The same goes for general purpose static functions being declared in specific classes.

In general an artificial coupling is a coupling between two modules that serves no direct purpose. It is a result of putting a variable, constant, or function in a temporarily convenient, though inappropriate, location. This is lazy and careless.

Take the time to figure out where functions, constants, and variables ought to be declared. Don’t just toss them in the most convenient place at hand and then leave them there.


[G13]: Искусственные привязки

То, что не зависит друг от друга, не должно объединяться искусственными привязками. Например, обобщенные перечисления не должны содержаться в более конкретных классах, потому что в этом случае информация о конкретном классе должна быть доступна в любой точке приложения, в которой используется перечисление. То же относится и к статическим функциям общего назначения, объявляемым в конкретных классах.

В общем случае искусственной считается привязка между двумя модулями, не имеющая явной, непосредственной цели. Искусственная привязка возникает в результате размещения переменной, константы или функции во временно удобном, но неподходящем месте. Главные причины для появления искусственных привязок — лень и небрежность.

Не жалейте времени — разберитесь, где должно располагаться объявление той или иной функции, константы или переменной. Слишком часто мы размещаем их в удобном месте «под рукой», а потом оставляем там навсегда.


[G13]: Штучні прив'язки

Те, що не залежить одне від одного, не повинно поєднуватися штучними прив'язками. Наприклад, узагальнені перерахування не повинні міститися в більш конкретних класах, тому що в цьому випадку інформація про конкретний клас повинна бути доступна в будь-якій точці програми, в якій використовується перерахування. Те саме стосується і статичних функцій загального призначення, що оголошуються в конкретних класах.

У випадку штучної вважається прив'язка між двома модулями, яка має явної, безпосередньої мети. Штучна прив'язка виникає внаслідок розміщення змінної, константи чи функції у тимчасово зручному, але невідповідному місці. Головні причини появи штучних прив'язок — лінь і недбалість.

Не шкодуйте часу — розберіться, де повинне розміщуватися оголошення тієї чи іншої функції, константи чи змінної. Занадто часто ми розміщуємо їх у зручному місці під рукою, а потім залишаємо там назавжди.


[G14]: Feature Envy

The methods of a class should be interested in the variables and functions of the class they belong to, and not the variables and functions of other classes. When a method uses accessors and mutators of some other object to manipulate the data within that object, then it envies the scope of the class of that other object. It wishes that it were inside that other class so that it could have direct access to the variables it is manipulating.

Example: C# | Java


[G14]: Функциональная зависть

Для методов класса должны быть важны переменные и функции того класса, которому они принадлежат, а не переменные и функции других классов. Когда метод использует методы доступа другого объекта для манипуляций с его данными, то он завидует области видимости класса этого объекта. Он словно мечтает находиться в другом классе, чтобы иметь прямой доступ к переменным, с которыми он работает.

Пример: C# | Java


[G14]: Функціональна заздрість

Для методів класу мають бути важливі змінні та функції того класу, якому вони належать, а не змінні та функції інших класів. Коли метод використовує методи доступу іншого об'єкта для маніпуляцій з його даними, то він заздрить області видимості класу цього об'єкта. Він ніби мріє бути в іншому класі, щоб мати прямий доступ до змінних, з якими він працює.

Приклад: C# | Java


[G15]: Selector Arguments

There is hardly anything more abominable than a dangling false argument at the end of a function call. What does it mean? What would it change if it were true? Not only is the purpose of a selector argument difficult to remember, each selector argument combines many functions into one. Selector arguments are just a lazy way to avoid splitting a large function into several smaller functions.

Example: C# | Java


[G15]: Аргументы-селекторы

Ничто так не раздражает, как висящий в конце вызова функции аргумент false. Зачем он здесь? Что изменится, если этот аргумент будет равен true? Смысл селектора трудно запомнить, но дело не только в этом — селектор указывает на объединение нескольких функций в одну. Аргументы-селекторы помогают ленивому программисту избежать разбиения большой функции на несколько меньших.

Пример: C# | Java


[G15]: Аргументи-селектори

Ніщо так не дратує, як аргумент false, що висить в кінці виклику функції. Для чого він тут? Що зміниться, якщо цей аргумент дорівнюватиме true? Сенс селектора важко запам'ятати, але справа не лише в цьому — селектор вказує на об'єднання кількох функцій в одну. Аргументи-селектори допомагають лінивому програмісту уникнути розбиття великої функції на кілька менших.

Приклад: C# | Java


[G16]: Obscured Intent

We want code to be as expressive as possible. Run-on expressions, Hungarian notation, and magic numbers all obscure the author’s intent.
It is worth taking the time to make the intent of our code visible to our readers.

Example: C# | Java


[G16]: Непонятные намерения

Код должен быть как можно более выразительным. Слишком длинные выражения, венгерская запись, «волшебные числа» — все это скрывает намерения автора. Не жалейте времени на то, чтобы сделать намерения своего кода максимально прозрачными для читателей.

Пример: C# | Java


[G16]: Незрозумілі наміри

Код має бути якомога виразнішим. Занадто довгі висловлювання, угорський запис, «чарівні числа» — це приховує наміри автора. Не шкодуйте часу, щоб зробити наміри свого коду максимально прозорими для читачів.

Приклад: C# | Java


[G17]: Misplaced Responsibility

One of the most important decisions a software developer can make is where to put code. The principle of least surprise comes into play here.

Example

Where should the PI constant go? Should it be in the Math class? Perhaps it belongs in the Trigonometry class? Or maybe in the Circle class?

Code should be placed where a reader would naturally expect it to be. The PI constant should go where the trig functions are declared. The OVERTIME_RATE constant should be declared in the HourlyPayCalculator class.

Sometimes we get “clever” about where to put certain functionality. We’ll put it in a function that’s convenient for us, but not necessarily intuitive to the reader.

Example

Perhaps we need to print a report with the total of hours that an employee worked. We could sum up those hours in the code that prints the report, or we could try to keep a running total in the code that accepts time cards.

One way to make this decision is to look at the names of the functions. Let’s say that our report module has a function named getTotalHours. Let’s also say that the module that accepts time cards has a saveTimeCard function. Which of these two functions, by it’s name, implies that it calculates the total? The answer should be obvious.

Clearly, there are sometimes performance reasons why the total should be calculated as time cards are accepted rather than when the report is printed. That’s fine, but the names of the functions ought to reflect this. For example, there should be a computeRunningTotalOfHours function in the timecard module.


[G17]: Неверное размещение

Одно из самых важных решений, принимаемых разработчиком, — выбор места для размещения кода. В игру вступает принцип наименьшего удивления. Код следует размещать там, где читатель ожидает его увидеть.

Пример

Где следует объявить константу PI? В классе Math? А может, ей место в классе Trigonometry? Или в классе Circle?

Константа PI должна находиться там, где объявляются тригонометрические функции. Константа OVERTIME_RATE объявляется в классе HourlyPayCalculator.

Иногда мы пытаемся «творчески» подойти к размещению функциональности. Мы размещаем ее в месте, удобном для нас, но это не всегда выглядит естественно для читателя кода.

Пример

Предположим, потребовалось напечатать отчет с общим количеством отработанных часов. Мы можем просуммировать часы в коде, печатающем отчет, или же накапливать сумму в коде обработки учетных карточек рабочего времени.

Чтобы принять решение, можно посмотреть на имена функций. Допустим, в модуле отчетов присутствует функция с именем getTotalHours, а в модуле обработки учетных карточек присутствует функция saveTimeCard. Какая из этих двух функций, если судить по имени, наводит на мысль о вычислении суммы? Ответ очевиден.

Очевидно, по соображениям производительности сумму правильнее вычислять при обработке карточек, а не при печати отчета. Все верно, но этот факт должен быть отражен в именах функций. Например, в модуле обработки учетных карточек должна присутствовать функция computeRunningTotalOfHours.


[G17]: Неправильне розміщення

Одне з найважливіших рішень, які приймає розробник, - вибір місця для розміщення коду. У гру входить принцип найменшого подиву. Код слід розміщувати там, де читач очікує побачити його.

Приклад

Де слід оголосити константу PI? У класі "Math"? А може, їй місце в класі Trigonometry? Або у класі Circle?

Константа PI повинна бути там, де оголошуються тригонометричні функції. Константа OVERTIME_RATE оголошується у класі HourlyPayCalculator.

Іноді ми намагаємось «творчо» підійти до розміщення функціональності. Ми розміщуємо її в зручному для нас місці, але це не завжди виглядає природно для читача коду.

Приклад

Припустимо, потрібно було надрукувати звіт із загальною кількістю відпрацьованих годин. Ми можемо підсумувати годинник у коді, що друкує звіт, або накопичувати суму в коді обробки облікових карток робочого часу.

Щоб прийняти рішення, можна переглянути імена функцій. Допустимо, у модулі звітів є функція з ім'ям getTotalHours, а в модулі обробки облікових карток присутня функція saveTimeCard. Яка з цих двох функцій, якщо судити на ім'я, наводить на думку про обчислення суми? Відповідь очевидна.

Вочевидь, з міркувань продуктивності суму правильніше обчислювати під час обробки карток, а чи не під час друку звіту. Все вірно, але цей факт має бути відображений в іменах функцій. Наприклад, у модулі обробки облікових карток має бути функція computeRunningTotalOfHours.


[G18]: Inappropriate Static

Math.max(double a, double b) is a good static method. It does not operate on a single instance; indeed, it would be silly to have to say new Math().max(a,b) or even a.max(b). All the data that max uses comes from its two arguments, and not from any “owning” object. More to the point, there is almost no chance that we’d want Math.max to be polymorphic.

Sometimes, however, we write static functions that should not be static.

Example: C# | Java

In general you should prefer nonstatic methods to static methods. When in doubt, make the function nonstatic. If you really want a function to be static, make sure that there is no chance that you’ll want it to behave polymorphically.


[G18]: Неуместные статические методы

Math.max(double a, double b) — хороший статический метод. Он работает не с одним экземпляром; в самом деле, запись вида new Math().max(a,b) или даже a.max(b) выглядела бы довольно глупо. Все данные, используемые max, берутся из двух аргументов, а не из некоего объекта-«владельца». А главное, что метод Math.max почти наверняка не потребуется делать полиморфным.

Но иногда мы пишем статические функции, которые статическими быть не должны.

Пример: C# | Java

В общем случае отдавайте предпочтение нестатическим методам перед статическими. Если сомневаетесь, сделайте функцию нестатической. Если вы твердо уверены, что функция должна быть статической, удостоверьтесь в том, что от нее не потребуется полиморфное поведение.


[G18]: Недоречні статичні методи

Math.max(double a, double b) — добрий статичний метод. Він працює не з одним екземпляром; насправді, запис виду new Math().max(a,b) або навіть a.max(b) виглядав би досить безглуздо. Усі дані, що використовуються max, беруться з двох аргументів, а не з якогось об'єкта-власника. А головне, що метод Math.max майже напевно не потрібно робити поліморфним.

Але іноді ми пишемо статичні функції, які статичними не повинні бути.

Приклад: C# | Java

Загалом віддавайте перевагу нестатичним методам перед статичними. Якщо ви сумніваєтеся, зробіть функцію нестатичною. Якщо ви впевнені, що функція повинна бути статичною, переконайтеся, що від неї не вимагатиметься поліморфна поведінка.


[G19]: Use Explanatory Variables

One of the more powerful ways to make a program readable is to break the calculations up into intermediate values that are held in variables with meaningful names.

Example: C# | Java

The simple use of explanatory variables makes it clear that the first matched group is the key, and the second matched group is the value.

It is hard to overdo this. More explanatory variables are generally better than fewer. It is remarkable how an opaque module can suddenly become transparent simply by breaking the calculations up into well-named intermediate values.


[G19]: Используйте пояснительные переменные

Один из самых эффективных способов улучшения удобочитаемости программы заключается в том, чтобы разбить обработку данных на промежуточные значения, хранящиеся в переменных с содержательными именами.

Пример: C# | Java

Простое использование пояснительных переменных четко объясняет, что первое совпадение содержит ключ key, а второе — значение value. Перестараться в применении пояснительных переменных трудно. Как правило, чем больше пояснительных переменных, тем лучше. Поразительно, насколько очевидным иногда становится самый невразумительный модуль от простого разбиения обработки данных на промежуточные значения с удачно выбранными именами.


[G19]: Використовуйте пояснювальні змінні

Один з найефективніших способів поліпшення зручності читання програми полягає в тому, щоб розбити обробку даних на проміжні значення, що зберігаються в змінних з змістовними іменами.

Приклад: C# | Java

Просте використання пояснювальних змінних чітко пояснює, що перший збіг містить ключ key, а друге значення value. Перестаратися у застосуванні пояснювальних змінних важко. Як правило, чим більше пояснювальних змінних, тим краще. На диво, наскільки очевидним іноді стає найнезрозуміліший модуль від простого розбиття обробки даних на проміжні значення з вдало обраними іменами.


[G20]: Function Names Should Say What They Do

If you have to look at the implementation (or documentation) of the function to know what it does, then you should work to find a better name or rearrange the functionality so that it can be placed in functions with better names.

Example: C# | Java


[G20]: Имена функций должны описывать выполняемую операцию

Если вам приходится обращаться к реализации (или документации), чтобы понять, что делает та или иная функция, постарайтесь найти более удачное имя или разбейте функциональность на меньшие функции с более понятными именами.

Пример: C# | Java


[G20]: Імена функцій повинні описувати виконувану операцію

Якщо вам доводиться звертатися до реалізації (або документації), щоб зрозуміти, що робить та чи інша функція, постарайтеся знайти вдале ім'я або розбийте функціональність на менші функції з більш зрозумілими іменами.

Приклад: C# | Java


[G21]: Understand the Algorithm

Lots of very funny code is written because people don’t take the time to understand the algorithm. They get something to work by plugging in enough if statements and flags, without really stopping to consider what is really going on.

Programming is often an exploration. You think you know the right algorithm for something, but then you wind up fiddling with it, prodding and poking at it, until you get it to “work.” How do you know it “works”? Because it passes the test cases you can think of.

There is nothing wrong with this approach. Indeed, often it is the only way to get a function to do what you think it should. However, it is not sufficient to leave the quotation marks around the word “work.”

Before you consider yourself to be done with a function, make sure you understand how it works. It is not good enough that it passes all the tests. You must know that the solution is correct.

Often the best way to gain this knowledge and understanding is to refactor the function into something that is so clean and expressive that it is obvious how it works.


[G21]: Понимание алгоритма

Очень много странного кода пишется из-за того, что люди не утруждают себя пониманием алгоритмов. Они заставляют программу работать «грубой силой», набивая ее командами if и флагами, вместо того чтобы остановиться и подумать, что же в действительности происходит.

Программирование часто сопряжено с исследованиями. Вы думаете, что знаете подходящий алгоритм для решения задачи, но потом вам приходится возиться с ним, подправлять и затыкать щели, пока вы не заставите его «работать». А как вы определили, что он «работает»? Потому что алгоритм прошел все тесты, которые вы смогли придумать.

В этом подходе нет ничего плохого. Более того, часто только так удается заставить функцию делать то, что она должна делать (по вашему мнению). Однако ограничиться «работой» в кавычках недостаточно.

Прежде чем откладывать в сторону готовую функцию, убедитесь в том, что вы понимаете, как она работает. Прохождения всех тестов недостаточно. Вы должны знать, что ваше решение правильно.

Один из лучших способов достичь этого знания и понимания — разбить функцию на фрагменты настолько чистые и выразительные, что вам станет совершенно очевидно, как работает данная функция.


[G21]: Розуміння алгоритму

Дуже багато дивного коду пишеться через те, що люди не турбують себе розумінням алгоритмів. Вони змушують програму працювати «грубою силою», набиваючи її командами if та прапорами, замість того щоб зупинитися та подумати, що ж насправді відбувається.

Програмування часто пов'язане із дослідженнями. Ви думаєте, що знаєте відповідний алгоритм для вирішення завдання, але потім вам доводиться возитися з ним, підправляти і затикати щілини, поки ви не змусите його «працювати». А як ви визначили, що він працює? Тому що алгоритм пройшов усі тести, які ви змогли вигадати.

У цьому підході нема нічого поганого. Більше того, часто тільки так вдається змусити функцію робити те, що вона має робити (на вашу думку). Однак обмежитися «роботою» у лапках недостатньо.

Перш ніж відкладати готову функцію, переконайтеся в тому, що ви розумієте, як вона працює. Проходження всіх тестів недостатньо. Ви повинні знати, що ваше рішення правильно.

Один з кращих способів досягти цього знання та розуміння – розбити функцію на фрагменти настільки чисті та виразні, що вам стане цілком очевидно, як працює дана функція.


[G22]: Make Logical Dependencies Physical

If one module depends upon another, that dependency should be physical, not just logical. The dependent module should not make assumptions (in other words, logical dependencies) about the module it depends upon. Rather it should explicitly ask that module for all the information it depends upon.

Example: C# | Java


[G22]: Преобразование логических зависимостей в физические

Если один модуль зависит от другого, зависимость должна быть не только логической, но и физической. Зависимый модуль не должен делать никаких предположений (иначе говоря, создавать логические зависимости) относительно того модуля, от которого он зависит. Вместо этого он должен явно запросить у этого модуля всю необходимую информацию.

Пример: C# | Java


[G22]: Перетворення логічних залежностей на фізичні

Якщо один модуль залежить від іншого, залежність має бути не лише логічною, а й фізичною. Залежний модуль не повинен робити жодних припущень (інакше кажучи, створювати логічні залежності) щодо модуля, від якого він залежить. Натомість він повинен явно запросити у цього модуля всю необхідну інформацію.

Приклад: C# | Java


[G23]: Prefer Polymorphism to If/Else or Switch/Case

First, most people use switch statements because it’s the obvious brute force solution, not because it’s the right solution for the situation. So this heuristic is here to remind us to consider polymorphism before using a switch.

Second, the cases where functions are more volatile than types are relatively rare. So every switch statement should be suspect.

I use the following “ONE SWITCH” rule: There may be no more than one switch statement for a given type of selection. The cases in that switch statement must create polymorphic objects that take the place of other such switch statements in the rest of the system.

Example: C# | Java


[G23]: Используйте полиморфизм вместо if/Else или switch/Case

Во-первых, команды switch чаще всего используются только потому, что они представляют очевидное решение методом «грубой силы», а не самое уместное решение для конкретной ситуации. Таким образом, это эвристическое правило напоминает нам о том, что до применения switch следует рассмотреть возможность применения полиморфизма.

Во-вторых, ситуации, в которых состав функций менее стабилен, чем состав типов, встречаются относительно редко. Следовательно, к каждой конструкции switch следует относиться с подозрением.

Я использую правило «ОДНОЙ КОМАНДЫ SWITCH»: для каждого типа выбора программа не должна содержать более одной команды switch. Множественные конструкции switch следует заменять полиморфными объектами.

Пример: C# | Java


[G23]: Використовуйте поліморфізм замість if/Else або switch/Case

По-перше, команди switch найчастіше використовуються тільки тому, що вони представляють очевидне рішення методом «грубою сили», а не найдоречніше рішення для конкретної ситуації. Таким чином, це евристичне правило нагадує нам про те, що до застосування switch слід розглянути можливість застосування поліморфізму.

По-друге, ситуації, у яких склад функцій менш стабільний, ніж склад типів, трапляються щодо рідко. Отже, до кожної конструкції switch слід ставитись з підозрою.

Я використовую правило "ОДНІЙ КОМАНДИ SWITCH": для кожного типу вибору програма не повинна містити більше однієї команди switch. Множинні конструкції switch слід замінювати поліморфними об'єктами.

Приклад: C# | Java


[G24]: Follow Standard Conventions

Every team should follow a coding standard based on common industry norms. This coding standard should specify things like where to declare instance variables; how to name classes, methods, and variables; where to put braces; and so on. The team should not need a document to describe these conventions because their code provides the examples.

Everyone on the team should follow these conventions. This means that each team member must be mature enough to realize that it doesn’t matter a whit where you put your braces so long as you all agree on where to put them.


[G24]: Соблюдайте стандартные конвенции

Все рабочие группы должны соблюдать единые стандарты кодирования, основанные на отраслевых нормах. Стандарт кодирования определяет, где объявляются переменные экземпляров; как присваиваются имена классов, методов и переменных; где размещаются фигурные скобки и т. д. Документ с явным описанием этих правил не нужен — сам код служит примером оформления.

Правила должны соблюдаться всеми участниками группы. Это означает, что каждый участник группы должен быть достаточно разумным, чтобы понимать: неважно, как именно размещаются фигурные скобки, если только все согласились размещать их одинаковым образом.


[G24]: Дотримуйтесь стандартних конвенцій

Усі робочі групи повинні дотримуватись єдиних стандартів кодування, заснованих на галузевих нормах. Стандарт кодування визначає, де оголошуються змінні екземплярів; як присвоюються імена класів, методів та змінних; де розміщуються фігурні дужки тощо. буд. Документ із явним описом цих правил не потрібен — сам код є прикладом оформлення.

Правила повинні дотримуватись усіма учасниками групи. Це означає, що кожен учасник групи повинен бути достатньо розумним, щоб розуміти: неважливо, як саме розміщуються фігурні дужки, якщо всі погодилися розміщувати їх однаковим чином.


[G25]: Replace Magic Numbers with Named Constants

In general it is a bad idea to have raw numbers in your code. You should hide them behind well-named constants.

Example

The number 86,400 should be hidden behind the constant SECONDS_PER_DAY. If you are printing 55 lines per page, then the constant 55 should be hidden behind the constant LINES_PER_PAGE.

Some constants are so easy to recognize that they don’t always need a named constant to hide behind so long as they are used in conjunction with very self-explanatory code.

Example: C# | Java

The term “Magic Number” does not apply only to numbers. It applies to any token that has a value that is not self-describing.

Example: C# | Java


[G25]: Заменяйте «волшебные числа» именованными константами

В общем случае присутствие «сырых» чисел в коде нежелательно. Числа следует скрыть в константах с содержательными именами.

Пример

Число 86,400 следует скрыть в константе SECONDS_PER_DAY. Если в странице отчета выводится 55 строк, число 55 следует скрыть в константе LINES_PER_ PAGE.

Некоторые числа так легко узнаются, что их не обязательно скрывать за именованными константами — при условии, что они используются в сочетании с предельно ясным кодом.

Пример: C# | Java

Термин «волшебное число» относится не только к числам. Он распространяется на все лексемы, значения которых не являются самодокументирующимися.

Пример: C# | Java


[G25]: Замінюйте «чарівні числа» іменованими константами

Загалом присутність «сирих» чисел у коді небажана. Числа слід приховати у константах із змістовними іменами.

Приклад

Число 86,400 слід приховати у константі SECONDS_PER_DAY. Якщо у сторінці звіту виводиться 55 рядків, число 55 слід приховати у константі LINES_PER_ PAGE.

Деякі числа так легко впізнаються, що їх не обов'язково приховувати за іменованими константами - за умови, що вони використовуються в поєднанні з ясним кодом.

Приклад: C# | Java

Термін "чарівне число" відноситься не тільки до чисел. Він поширюється на всі лексеми, значення яких не є самодокументуючими.

Приклад: C# | Java


[G26]: Be Precise

Expecting the first match to be the only match to a query is probably naive. Using floating point numbers to represent currency is almost criminal. Avoiding locks and/or transaction management because you don’t think concurrent update is likely is lazy at best. Declaring a variable to be an ArrayList when a List will due is overly constraining. Making all variables protected by default is not constraining enough.

When you make a decision in your code, make sure you make it precisely. Know why you have made it and how you will deal with any exceptions. Don’t be lazy about the precision of your decisions. If you decide to call a function that might return null, make sure you check for null. If you query for what you think is the only record in the database, make sure your code checks to be sure there aren’t others. If there is the possibility of concurrent update, make sure you implement some kind of locking mechanism.

Ambiguities and imprecision in code are either a result of disagreements or laziness. In either case they should be eliminated.


[G26]: Будьте точны

Наивно ожидать, что первая запись, возвращаемая по запросу, является единственной. Использовать числа c плавающей точкой для представления денежных сумм — почти преступление. Отсутствие блокировок и/или управления транзакциями только потому, что вы думаете, что одновременное обновление маловероятно — в лучшем случае халатность. Объявление переменной с типом ArrayList там, где более уместен тип List — чрезмерное ограничение. Объявление всех переменных защищенными по умолчанию — недостаточное ограничение.

Принимая решение в своем коде, убедитесь в том, что вы действуете предельно точно и аккуратно. Знайте, почему принимается решение, и как вы собираетесь поступать с исключениями из правила. Не ленитесь обеспечивать точность своих решений. Если вы решили вызвать функцию, которая может вернуть null — проверьте возвращаемое значение. Если вы запрашиваете из базы данных запись, которая, по вашему мнению, является единственной — проверьте, не вернул ли запрос дополнительные записи. Если в программе существует возможность одновременного объявления, реализуйте ту или иную разновидность блокировки. Неоднозначности и неточности в коде объясняются либо недопониманием, либо ленью. В любом случае от них следует избавиться.


[G26]: Будьте точні

Наївно очікувати, що перший запис, який повертається на запит, є єдиним. Використовувати числа з плаваючою точкою для подання грошових сум – майже злочин. Відсутність блокувань та/або керування транзакціями лише тому, що ви думаєте, що одночасне оновлення малоймовірне — у кращому разі недбалість. Оголошення змінної з типом ArrayList там, де доречніший тип List — надмірне обмеження. Оголошення всіх змінних захищеними за умовчанням – недостатнє обмеження.

Приймаючи рішення у своєму коді, переконайтеся, що ви дієте гранично точно і акуратно. Знайте, чому приймається рішення, і як ви збираєтеся діяти з винятками з правила. Не лінуйтеся забезпечувати точність своїх рішень. Якщо ви вирішили викликати функцію, яка може повернути null — перевірте повертане значення. Якщо ви запитуєте з бази даних запис, який, на вашу думку, єдиний — перевірте, чи не повернув запит додаткові записи. Якщо у програмі існує можливість одночасного оголошення, реалізуйте той чи інший різновид блокування. Неоднозначності та неточності в коді пояснюються або непорозумінням, або лінню. У будь-якому випадку їх слід позбутися.


[G27]: Structure over Convention

Enforce design decisions with structure over convention. Naming conventions are good, but they are inferior to structures that force compliance. For example, switch/cases with nicely named enumerations are inferior to base classes with abstract methods. No one is forced to implement the switch/case statement the same way each time; but the base classes do enforce that concrete classes have all abstract methods implemented.


[G27]: Структура важнее конвенций

Воплощайте архитектурные решения на уровне структуры кода; она важнее стандартов и конвенций. Содержательные имена полезны, но структура, заставляющая пользователя соблюдать установленные правила, важнее. Например, конструкции switch/case с хорошо выбранными именами элементов перечисления уступают базовым классам с абстрактными методами. Ничто не вынуждает пользователя применять одинаковую реализацию switch/case во всех случаях; с другой стороны, базовые классы заставляют его реализовать все абстрактные методы в конкретных классах.


[G27]: Структура важливіша за конвенції

Втілюйте архітектурні рішення лише на рівні структури коду; вона важливіша за стандарти та конвенції. Змістові імена корисні, але структура, яка змушує користувача дотримуватися встановлених правил, важливіше. Наприклад, конструкції switch/case з добре вибраними іменами елементів перерахування поступаються базовим класам з абстрактними методами. Ніщо не змушує користувача застосовувати однакову реалізацію switch/case у всіх випадках; з іншого боку, базові класи змушують його реалізувати всі абстрактні методи у конкретних класах.


[G28]: Encapsulate Conditionals

Boolean logic is hard enough to understand without having to see it in the context of an if or while statement. Extract functions that explain the intent of the conditional.

Example: C# | Java


[G28]: Инкапсулируйте условные конструкции

В булевой логике достаточно трудно разобраться и вне контекста команд if или while. Выделите в программе функции, объясняющие намерения условной конструкции.

Пример: C# | Java


[G28]: Інкапсулюйте умовні конструкції

У булевій логіці досить важко розібратися і поза контекстом команд if чи while. Виділіть у програмі функції, які пояснюють наміри умовної конструкції.

Приклад: C# | Java


[G29]: Avoid Negative Conditionals

Negatives are just a bit harder to understand than positives. So, when possible, conditionals should be expressed as positives.

Example: C# | Java


[G29]: Избегайте отрицательных условий

Отрицательные условия немного сложнее для понимания, чем положительные. Таким образом, по возможности старайтесь формулировать положительные условия.

Пример: C# | Java


[G29]: Уникайте негативних умов

Негативні умови трохи складніші для розуміння, ніж позитивні. Таким чином, наскільки можна намагайтеся формулювати позитивні умови.

Приклад: C# | Java


[G30]: Functions Should Do One Thing

It is often tempting to create functions that have multiple sections that perform a series of operations. Functions of this kind do more than one thing, and should be converted into many smaller functions, each of which does one thing.

Example: C# | Java


[G30]: Функции должны выполнять одну операцию

Часто возникает искушение разделить свою функцию на несколько секций для выполнения разных операций. Такие функции выполняют несколько операций; их следует преобразовать в группу меньших функций, каждая из которых выполняет только одну операцию.

Пример: C# | Java


[G30]: Функції повинні виконувати одну операцію

Часто виникає спокуса розділити свою функцію на кілька секцій для виконання різних операцій. Такі функції виконують кілька операцій; їх слід перетворити на групу менших функцій, кожна з яких виконує лише одну операцию.

Приклад: C# | Java


[G31]: Hidden Temporal Couplings

Temporal couplings are often necessary, but you should not hide the coupling. Structure the arguments of your functions such that the order in which they should be called is obvious.

Example: C# | Java


[G31]: Скрытые временные привязки

Временные привязки часто необходимы, но они не должны скрываться. Структура аргументов функций должна быть такой, чтобы последовательность вызова была абсолютно очевидной.

Пример: C# | Java


[G31]: Приховані тимчасові прив'язки

Тимчасові прив'язки часто потрібні, але вони не повинні ховатися. Структура аргументів функцій має бути такою, щоб послідовність виклику була абсолютно очевидною.

Приклад: C# | Java


[G32]: Don’t Be Arbitrary

Have a reason for the way you structure your code, and make sure that reason is communicated by the structure of the code. If a structure appears arbitrary, others will feel empowered to change it. If a structure appears consistently throughout the system, others will use it and preserve the convention.

Example: C# | Java


[G32]: Структура кода должна быть обоснована

Структура кода должна выбираться не произвольно, а по строго определенным причинам. Позаботьтесь о том, чтобы эти причины были выражены в структуре кода. Если при чтении кода создается впечатление, что его структура выбрана произвольно, другим пользователям может показаться, что ее можно изменить. Если во всей системе последовательно используется единая структура кода, другие пользователи примут ее и сохранят действующие правила.

Пример: C# | Java


[G32]: Структура коду має бути обґрунтована

Структура коду повинна вибиратися не довільно, а з певних причин. Подбайте про те, щоб ці причини були виражені у структурі коду. Якщо при читанні коду створюється враження, що його структура обрана довільно, іншим користувачам може здатися, що її можна змінити. Якщо у всій системі послідовно використовується єдина структура коду, інші користувачі ухвалять її та збережуть чинні правила.

Приклад: C# | Java


[G33]: Encapsulate Boundary Conditions

Boundary conditions are hard to keep track of. Put the processing for them in one place. Don’t let them leak all over the code. We don’t want swarms of +1s and -1s scattered hither and yon.

Example: C# | Java


[G33]: Инкапсулируйте граничные условия

Отслеживать граничные условия нелегко. Разместите их обработку в одном месте. Не позволяйте им «растекаться» по всему коду. Не допускайте, чтобы в вашей программе кишели многочисленные +1 и –1.

Пример: C# | Java


[G33]: Інкапсулюйте граничні умови

Відстежувати граничні умови нелегко. Розмістіть їхню обробку в одному місці. Не дозволяйте їм розтікатися по всьому коду. Не допускайте, щоб у вашій програмі кишели численні +1 та –1.

Приклад: C# | Java


[G34]: Functions Should Descend Only One Level of Abstraction

The statements within a function should all be written at the same level of abstraction, which should be one level below the operation described by the name of the function.

Example: C# | Java


[G34]: Функции должны быть написаны на одном уровне абстракции

Все команды функции должны быть сформулированы на одном уровне абстракции, который расположен одним уровнем ниже операции, описываемой именем функции.

Пример: C# | Java


[G34]: Функції мають бути написані на одному рівні абстракції

Усі команди функції повинні бути сформульовані на одному рівні абстракції, який розташований одним рівнем нижче операції, що описується ім'ям функції.

Приклад: C# | Java


[G35]: Keep Configurable Data at High Levels

If you have a constant such as a default or configuration value that is known and expected at a high level of abstraction, do not bury it in a low-level function. Expose it as an argument to that low-level function called from the high-level function.

Example: C# | Java


[G35]: Храните конфигурационные данные на высоких уровнях

Если в программе имеется константа, определяющая значение по умолчанию или параметр конфигурации, и эта константа известна на высоких уровнях абстракции, — не прячьте ее в низкоуровневой функции. Передайте ее в аргументе низкоуровневой функции, вызываемой из функции высокого уровня.

Пример: C# | Java


[G35]: Зберігайте конфігураційні дані на найвищих рівнях

Якщо у програмі є константа, яка визначає значення за промовчанням або параметр конфігурації, і ця константа відома на високих рівнях абстракції, не ховайте її в низькорівневій функції. Передайте її в аргументі низькорівневої функції, що викликається з високого рівня.

Приклад: C# | Java


[G36]: Avoid Transitive Navigation

In general we don’t want a single module to know much about its collaborators. More specifically, if A collaborates with B, and B collaborates with C, we don’t want modules that use A to know about C. (For example, we don’t want a.getB().getC().doSomething();).

This is sometimes called the Law of Demeter. The Pragmatic Programmers call it “Writing Shy Code.” In either case it comes down to making sure that modules know only about their immediate collaborators and do not know the navigation map of the whole system.

If many modules used some form of the statement a.getB().getC(), then it would be difficult to change the design and architecture to interpose a Q between B and C. You’d have to find every instance of a.getB().getC() and convert it to a.getB().getQ().getC(). This is how architectures become rigid. Too many modules know too much about the architecture.

Rather we want our immediate collaborators to offer all the services we need. We should not have to roam through the object graph of the system, hunting for the method we want to call. Rather we should simply be able to say:

myCollaborator.doSomething().

[G36]: Избегайте транзитивных обращений

В общем случае модуль не должен обладать слишком полной информацией о тех компонентах, с которыми он взаимодействует. Точнее, если A взаимодействует с B, а B взаимодействует с C, то модули, использующие A, не должны знать о C (то есть нежелательны конструкции вида a.getB().getC().doSomething();).

Иногда это называется «законом Деметры». Прагматичные программисты используют термин «умеренный код». В любом случае все сводится к тому, что модули должны обладать информацией только о тех модулях, с которыми они непосредственно взаимодействуют, а не располагать навигационной картой всей системы.

Если в нескольких модулях используется та или иная форма команды a.getB(). getC(), то в дальнейшем вам будет трудно изменить архитектуру системы, вставив между B и C промежуточный компонент Q. Придется найти каждое вхождение a.getB().getC() и преобразовать его в a.getB().getQ().getC(). Так образуются жесткие, закостеневшие архитектуры. Слишком многие модули располагают слишком подробной информацией о системе.

Весь необходимый сервис должен предоставляться компонентами, с которыми напрямую взаимодействует модуль. Не заставляйте пользователя странствовать по графу объектов системы в поисках нужного метода. Проблема должна решаться простыми вызовами вида

myCollaborator.doSomething()

[G36]: Уникайте транзитивних звернень

У загальному випадку модуль не повинен мати надто повну інформацію про ті компоненти, з якими він взаємодіє. Точніше, якщо A взаємодіє з B, а B взаємодіє з C, то модулі, що використовують A, не повинні знати про C (тобто небажані конструкції виду a.getB().getC().doSomething();) .

Іноді це називається "законом Деметри". Прагматичні програмісти використовують термін "помірний код". У будь-якому випадку все зводиться до того, що модулі повинні мати інформацію тільки про ті модулі, з якими вони безпосередньо взаємодіють, а не мати навігаційну карту всієї системи.

Якщо у кількох модулях використовується та чи інша форма команди a.getB(). getC(), то надалі вам буде важко змінити архітектуру системи, вставивши між B і C проміжний компонент Q. Доведеться знайти кожне входження a.getB().getC() і перетворити його на a. getB().getQ().getC(). Так утворюються жорсткі, закостенілі архітектури. Занадто багато модулів мають занадто докладну інформацію про систему.

Весь необхідний сервіс повинен надаватися компонентами, з якими взаємодіє модуль. Не змушуйте користувача мандрувати графом об'єктів системи у пошуках потрібного методу. Проблема повинна вирішуватись простими викликами виду

myCollaborator.doSomething()