Рецепты хорошего кода
(Конспект лекции, на которой происходил разбор основных проблем и ошибок, возникших при выполнении домашнего задания.)
Качество написанного кода складывается из трех главных составляющих: понятности, эффективности и правильности.
1 Понятный код
Код должен быть понятным, потому что так в нем легче искать ошибки, читать и модифицировать. Далее представлены некоторые советы, помогающие сделать код более понятным.
1.1 Используйте каскады if
ПЛОХО | ХОРОШО |
---|---|
if (...) { ... } else if (...) { ... } else { ... } } |
if (...) { ... } else if (...) { ... } else { ... } |
Если нужно проверить много равноправных условий, лучше оформлять их не "лесенкой", а каскадом. С точки зрения компилятора эти два варианта не различаются, но правый вариант намного проще читать и понимать.
1.2 Не злоупотребляйте break и continue
ПЛОХО | ХОРОШО |
---|---|
for (...; ...; ...) { if (...) { ... continue; } if (...) { ... continue; } ... } |
for (...; ...; ...) { if (...) { ... } else if (...) { ... } else { ... } } |
Код слева выглядит уже лучше, чем в примере 1.1, но в нем слишком часто встречается оператор continue.
Операторы break и continue стоит употреблять только в аварийных ситуациях, когда встретилось что-то, что мы не умеем осмысленно обрабатывать и хотим пропустить.
1.3 Не выпендривайтесь
ПЛОХО | ХОРОШО |
---|---|
x &= 0; |
x = 0; |
Конструкции вроде x&=0 трудно понять.
Эта конструкция иногда допустима для единообразия, например, если в коде часто встречаются строки вида x&=a и вы хотите подчеркнуть их взаимосвязь.
1.4 Если вы применяете к числам битовые операции, записывайте их в 16-ичной форме
ПЛОХО | ХОРОШО |
---|---|
x &= 64; |
x &= 0x40; x &= (1 << 6); |
Шестнадцатеричное число гораздо нагляднее отображает смысл битовых операций, по нему проще понять, как выглядит число в двоичной системе.
Кроме того, употребление шестнадцатеричных чисел подчеркивает, что здесь действительно нужна битовая операция, что это не опечатка, и обращает на нее внимание.
1.5 Не пишите сложно
ПЛОХО | ХОРОШО |
---|---|
//это невозможно понять int x = (~0)^((1 << 7) + (1 << 6)); |
int x = 0x3F; |
Не нужно без необходимости писать выражения, для понимания которых приходится прикладывать усилия. Постарайтесь найти более простой для восприятия вариант.
1.6
ПЛОХО | ХОРОШО |
---|---|
if (a < b) return false; return true; |
return a >= b; |
Даже несмотря на то, что запись слева может быть для кого-то понятнее записи справа, ее не стоит использовать — она слишком длинная и многих раздражает.
1.7 Не пишите длинных строк
При чтении кода не должна возникать необходимость прокручивать экран вправо из-за того, что строка в него не влезает, поэтому слишком длинных строк следует избегать.
1.7.1 Не пишите длинных if'ов
ПЛОХО | ХОРОШО |
---|---|
if ((...) && (...) && (...) && (...) && (...) && (...)) { ... } |
if ((...) && (...) && (...) && (...) && (...) && (...)) { ... } |
Если в программе встречается очень длинное условие, лучше переносить каждую его часть на новую строчку, а не писать все в одной. В такой структуре проще разобраться, и она не уезжает за края экрана.
1.7.2 Не пишите комментарии справа
ПЛОХО | ХОРОШО |
---|---|
while (...) { //Такой комментарий ... //будет очень ... //неудобно редактировать ... //при удалении ... //или добавлении ... //строк в цикл } |
//А такой комментарий не зависит от изменений в коде while (...) { ... ... ... } |
Комментарии лучше всего писать непосредственно перед тем фрагментом кода, который вы хотите пояснить. Во-первых, это позволяет избежать слишком длинных строчек, неудобных для чтения, во-вторых, комментарии не будут съезжать при изменениях в коде.
1.8 Используйте сокращенный switch
ПЛОХО | ХОРОШО |
---|---|
switch (bt) { case 0: return 0; case 1: return 0; ... case 127: return 0; case -128: return 1; } |
switch (bt) { case 0: case 1: ... case 127: return 0; case -128: return 1; } |
Если при использовании оператора switch некоторые ветви возвращают одинаковые значения, можно использовать сокращенную версию, группируя такие ветви; такая запись короче и проще для понимания.
2 Правильный код
В этой части разобраны часто встречающиеся ошибки, из-за которых программа начинает работать неправильно, и моменты, о которых стоит помнить, чтобы такие ошибки не возникали.
2.1 Помните, что в Java все типы знаковые
ПЛОХО | ХОРОШО |
---|---|
byte[] array; int[256] table = {...}; for (int i = 0; i < N; i++) { count += table[array[i]]; } |
byte[] array; int[256] table = {...}; for (int i = 0; i < N; i++) { int bt = (array[i] > 0) ? array[i] : 256 + array[i]; count += table[bt]; } |
Число array[i] может оказаться отрицательным, и произойдет обращение за пределы массива table.
2.2 Различайте >> и >>>
ПЛОХО | ХОРОШО |
---|---|
while (N != 0) { count += N & 1; N = N >> 1; } |
while (N != 0) { count += N & 1; N = N >>> 1; } |
Цикл слева будет повторяться бесконечно, потому что оператор >> заполняет освободившиеся биты единицами, а не нулями, как >>>. В таком цикле число N никогда не станет равным нулю.
2.3 Запускайте свои программы
ПЛОХО | ХОРОШО |
---|---|
x = 1 << 7 + 1 << 6; |
x = (1 << 7) + (1 << 6); |
Строчка слева дает совсем не тот результат, который ожидался, потому что у сложения приоритет больше, чем у сдвига. Но дело не в том, что нужно помнить приоритет операций (лучше, в конце концов, просто ставить скобки), а в том, что эта ошибка заметна сразу, если хоть раз запустить программу. Программы обязательно нужно тестировать.
2.4 Думайте о крайних случаях
Программа может вести себя корректно в большинстве ситуаций, но при возникновении крайних случаев выдавать ошибки. Поэтому при тестировании обязательно нужно обращать особое внимание на крайние случаи (например, на случай пустого списка при работе со списками).
3 Эффективный код
Ниже описаны правила, которых нужно всегда придерживаться, чтобы не замедлять зря выполнение программы.
3.1 Думайте про количество действий в выражениях
ПЛОХО | ХОРОШО |
---|---|
//Сдвиг ычисляется по ходу выполнения y = 1 & (x << 7) |
//Сдвиг вычисляется при компиляции y = x & (1 << 7) |
Там, где это можно, константы нужно объединять.
3.2 Считайте обращения к памяти
Этот пример не на Java, а на C++.ПЛОХО | ХОРОШО |
---|---|
unsigned char mask = 0x80; while (x & mask) { ... } |
const unsigned char MASK = 0x80; while (x & MASK) { ... } |
При возможности нужно использовать константы, а не переменные, особенно если обращений к этим переменным много.
При использовании переменных мы тратим время на ненужные обращения к памяти.
3.3 Никогда не вызывайте рядом одну и ту же функцию
ПЛОХО | ХОРОШО |
---|---|
LinkedList<Byte> list; if ((list.get(i) > 0x04) || (list.get(i) < 0x08)) { ... } |
LinkedList<Byte> list; byte bt = list.get(i); if ((bt > 0x04) || (bt < 0x08)) { ... } |
Вызов функции — очень дорогая операция, и ее следует по возможности избегать.