Поиск  
Always will be ready notify the world about expectations as easy as possible: job change page
17 января 2019 г.

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

Источник:
Просмотров:
668

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

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

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

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

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

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

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

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

В программировании имена повсюду. Мы называем функции, классы, аргументы, пакеты и т. д. Иногда мы думаем что-то вроде: «И так понятно, что 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 информативность

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

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

С первым пунктом вроде все понятно, но давайте проясним. К ненужным проверкам относится в т. ч. проверка на 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, куда и коммитится проект, так что никаких проблем с привязкой Трэвиса к проекту нет.

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

Написать сообщение
Тип
Почта
Имя
*Сообщение