10 принципов хорошего программного кода, который устроит всех

Чтобы от программного кода не отмахивались собеседующие или коллеги, он должен быть удобоваримым. Как написать «конфетку», которая понравится всем?

Роберт Мартин когда-то сказал, что единственным допустимым измерением качества кода является «Что за…».

Позвольте объяснить. Всякий раз, когда на глаза попадается какой-либо код, возникает одна из трех эмоций:

  1. «Что за…», произнесенное с отвращением – этот код вообще не нужен.
  2. «Что за…», выражающее восхищение – «Эй, а ведь этот парень действительно крут!»
  3. «Что за…» с раздражением – в представленном коде невозможно разобраться.

Так что отвечает за реакцию на код? Это чистота кода. Писать чистый и предельно понятный код – признак настоящего профессионала. Как этого добиться?

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

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

«Могу ли я узнать ваше имя?»

В программировании имена повсюду. Мы называем функции, классы, аргументы, пакеты и т. д. Иногда мы думаем что-то вроде: «И так понятно, что text – это текстовое поле. Зачем мудрить?» Однако взглянув на код или часть программного кода через неделю, две или месяц, натыкаемся на абракадабру, в которой непонятно что непонятно за что отвечает.

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

setTitle(text1);
setSize(550,360);
setVisible(true);

text2.setEditable(false);

try {
     List<String> text = Files.readAllLines(Paths.get("text.txt"));
     name = text.get(0)+":";
     name2.setText(name);
} catch (IOException e) {
     e.printStackTrace();
}

В коде происходит нечто странное. Давайте немного изменим его:

setTitle(title);  //Здесь мы видим заголовок
setSize(550,360);
setVisible(true);

textAreaMessage.setEditable(false);  //Здесь уже JTextArea, etc.

try {
     List<String> mass = Files.readAllLines(Paths.get("text.txt"));
     name = mass.get(0)+":";
     labelName.setText(name);
} catch (IOException e) {
     e.printStackTrace();
}

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

Не бойтесь разбивать код на составляющие

Луис Салливан как-то сказал: «Форма следует за функцией».

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

Небольшой пример-визуализация сказанного. Работа со слушателем:

buttonSave.addActionListener(new ActionListener() {
  @Override
  public void actionPerformed(ActionEvent e) {
      buttonLogin();
  }
});

Просто выносим функцию нашей кнопки в отдельный метод:

protected void buttonLogin() {
   System.out.println("Вот так будет намного удобнее :)");
}

Комментирование программного кода

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

Также это большой плюс при приеме на работу. Выполняя ТЗ работодателя, не забывайте комментировать код: так вы показываете свою серьезность и умение писать код, понятный для всех.

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

/*** Расположение элементов ***/
panel.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();

c.gridx = 0;
c.gridy = 0;
panel.add(labelQuestion, c);
c.gridx = 0;
c.gridy = 1;
panel.add(labelSpace, c);

А вот вариант объяснения одной из строк:

FileWriter writer;
try {
     writer = new FileWriter("text.txt", false);
     writer.write(textName.getText());
     writer.flush();  //Финализирует выходное состояние, очищая все буферы вывода
} catch (IOException e) {
     e.printStackTrace();
}

Удобно, правда? Работодатель или напарники по проекту будут того же мнения.

Минимализм vs информативность

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

  1. Удаление лишних проверок и условий.
  2. Избегание дублирования.
  3. Умение пользоваться конкретным языком программирования.

С первым пунктом вроде все понятно, но давайте проясним. К ненужным проверкам относится в т. ч. проверка на null. Иногда это даже попахивает паранойей. Например:

obj = new Object();
...
if (obj != null) {
    obj.call();
}

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

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

Рекурсия – не панацея

Многие считают рекурсию лучшим средством для устранения всего лишнего. Если говорить о внешнем виде программного кода, это, несомненно, правда. Пример с факториалом:

public static void factorial() {  //итерация
   int fact = 1;
   int n = 5;

   for (int i = 1; i<=n; i++){
        fact*=i;
        System.out.println(fact);
   }
}
static int factorial(int n){  //рекурсия
   int res;

   if (n == 1) return 1;
   res = fact(n-1)*n;
   return res;
}

Да, в этом случае код и там, и там маленький, но если прибавить кучу условий и строк – все изменится. Например, вот рекурсивное решение ханойской башни:

public static void hanoi(int n, String a, String b, String c){
    if (n == 1) System.out.println("#" + n + " " + a + " -> " + c);
    else {
       hanoi(n - 1, a, c, b);
       System.out.println("#" + n + " " + a + " -> " + c);
       hanoi(n - 1, b, a, c);
    }
}

А теперь вообразите, сколько строк выдаст новичок, столкнувшийся с ханойской башней и работающий только с итерацией :)

Но не все так гладко. Попробуйте посчитать факториал большого числа. Вероятно, IDE зависнет, пытаясь переварить ваше решение, и это понятно: рекурсия «кушает» много памяти, так как метод каждый раз вызывает сам себя. Конечно, если в поставленной задаче рекурсия необходима (например, при обработке древовидных структур), использовать ее нужно, но ни в коем случае не злоупотреблять. Компактность-то она обеспечит, но что потом делать с памятью?

Не бойтесь перемен!

Здесь собраны очень простые рекомендации, о которых знает каждый, вот только далеко не каждый их использует. Допустим, вместо «многослойных» if-ов можно использовать оператор (x ? y : z).

Пример с if:

if (x) {
    var1 = y;
} else {
    var1 = z;
}

Пример с (x ? y : z):

var1 = x ? y : z;

Также не забывайте о существовании forEach(), который избавит вас от претензий в стиле «Многа букав»:

public static void forEach(){
    int [] mas = {1, 2, 3, 4, 5};
    for (int i : mas){
        System.out.println(i);
    }
}

Объединяйте вложенные if. Посмотрите, насколько проще становится код.

Было:

if (a) {
    if (b) {
        do_something();
    }
}

Стало:

if (a && b) {
    do_something();
}

Поговорим о боли под названием «try-catch»

Читаемость кода часто усугубляется повсеместными блоками try-catch, которые сильно портят «картинку». Кроме того, по мере чтения такого программного кода теряются цель и логика происходящего в нем. А все должно быть предельно понятным, особенно для стороннего человека. Правильно обрабатывать возможные ошибки – признак настоящего мастерства.

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

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

Логирование

Не пренебрегайте лог-файлами! Эти «ребята» всегда помогут в создании и сопровождении вашего будущего ПО, ведь на поиски и обработку ошибок будет уходить гораздо меньше времени.

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

Да, такие файлы предназначены для более сложных проектов, и в каких-нибудь Hello-world-программах просто не нужны. Но если вы разрабатываете приложение вдвоем или командой, либо это тестовое задание на допуск к собеседованию – займитесь логированием: интервьюер будет впечатлен, а команда – лишена необходимости переворачивать весь код вверх тормашками в поисках ошибки.

Тестирование

Юнит-тестирование (или модульное тестирование) – еще одна составляющая хорошего программного кода, которая заставит помучиться. Зато в итоге вы сможете запросто проверять на правильность отдельные модули.

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

Используйте бейджики качества кода

Часто в файлах README можно наблюдать небольшие иконки. Что же там делают данные графические элементы? Это бейджики, которые показывают, насколько хорош код. Зачастую используются либо перфекционистами, либо в серьезных проектах, но если даже незначительный по важности код прошел такую проверку – это огромный плюс.

Где получить бейджик? Для этого существуют специальные сервисы. Например, мне нравится Travis CI, предназначенный для сборки и тестирования ПО. Он использует в качестве хостинга GitHub, куда и коммитится проект, так что никаких проблем с привязкой Трэвиса к проекту нет.

Поначалу сложно понять, почему же сервис ругается и не пропускает код, но когда вы поймете, дальнейшее сотрудничество с сервисом пойдет как по маслу.

Вас также могут заинтересовать другие статьи по теме:

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

admin
30 июня 2018

Шаблоны проектирования в Python: для стильного кода

Многие шаблоны проектирования встроены в Python из коробки, а другие очень ...