Пожаловаться

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

16505
Пожаловаться

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

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

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

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

Логирование

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

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

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

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

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

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

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

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

10 принципов хорошего программного кода

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

10 принципов хорошего программного кода

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

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

16505

Комментарии

BUG!