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

Какой он, хороший код по мнению Линуса Торвальдса?

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

В своем недавнем интервью Линус Торвальдс сделал небольшое замечание о программировании с «хорошим вкусом». Хороший вкус? Интервьюер пытался выудить детали, и Линус не подкачал.

Он представил фрагмент кода. Но это не был код «хорошего вкуса». Этот код был примером плохого вкуса, чтобы представить некий первоначальный контраст.

Это функция, написанная на C, которая удаляет объект из связного списка. Она содержит 10 строк кода.

Торвальдс обратил внимание на if в самом низу. Это было if -утверждение, которое он и критиковал.
Я приостановил видео и изучил слайд. Недавно я написал очень похожий код. Линус фактически говорил, что у меня плохой вкус. Я переступил через свою гордость и продолжил видео.

Линус объяснил аудитории, что при удалении объекта из связного списка есть два случая, которые следует рассмотреть. Если объект находится в начале списка, то процесс его удаления будет отличаться от удаления из середины списка. Это и является причиной «плохого вкуса» утверждения if.
Но если он признает, что это необходимо, то почему это так плохо?

Затем он показал второй слайд аудитории. Это был его пример той же функции, но написанный с «хорошим вкусом».

Исходные 10 строк кода теперь уменьшены до 4.
Но не за счет важных строк. Все дело в if -утверждении. Оно исчезло. Оно больше не нужно. Код был реорганизован таким образом, что процесс удаления будет происходить независимо от позиции объекта в списке.

Линус объяснил новый код, удалив нижнюю часть кода, и все. Затем интервью продолжилось дальше.
Я изучил код. Линус был прав. Второй слайд был лучше. Если бы это был тест по отличию хорошего вкуса от плохого, я бы потерпел крах. Мысль о том, что можно исключить это условное утверждение никогда не приходила мне в голову.

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

Так я вернулся и просмотрел код в своем последнем проекте. Мой код был также написан на C.
Насколько я понял, сутью требования «хорошего вкуса» является устранение крайних случаев, которые, как правило, проявляют себя, как условные утверждения. Чем меньше условных операторов, тем «вкуснее» ваш код.

Вот один конкретный пример улучшения, которое я сделал и хотел поделиться.

Инициализация краев сетки

Ниже приведен алгоритм, который я написал для инициализации точек вдоль краев сетки, представленной в виде многомерного массива: grid[rows][cols]

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

Для этого я изначально зациклился на каждой точке сетки и использовал условные обозначения для проверки границ. Вот как это выглядело:

for (r = 0; r < GRID_SIZE; ++r) {
    for (c = 0; c < GRID_SIZE; ++c) {
        // Верхняя грань
        if (r == 0)
            grid[r][c] = 0;
        // Левая грань
        if (c == 0)
            grid[r][c] = 0;
        // Правая грань
        if (c == GRID_SIZE - 1)
            grid[r][c] = 0;
        // Нижняя грань
        if (r == GRID_SIZE - 1)
            grid[r][c] = 0;
    }
}

 

Он работает, но смотря на него сейчас, я понимаю, что есть некие проблемы с конструкцией.

  1. Сложность - использование 4 условных операторов внутри 2 циклов кажется слишком сложным.
  2. Эффективность - учитывая, что GRID_SIZE имеет значение 64, этот цикл выполняет 4096 итераций, чтобы установить значения только для 256 краевых точек.

Линус, вероятно, согласится, что это не очень «вкусно».
Мне пришлось немного потрудиться. После этого я смог немного упростить один цикл, содержащий 4 условия. Улучшение было незначительным в плане сложности, но достаточным для производительности, потому что оно выполняло только 256 итераций цикла, по одной для каждой точки по краю.

for (i = 0; i < GRID_SIZE * 4; ++i) {
    // Верхняя грань
    if (i < GRID_SIZE)
        grid[0][i] = 0;
    // Правая грань
    else if (i < GRID_SIZE * 2)
        grid[i - GRID_SIZE][GRID_SIZE - 1] = 0;
    // Левая грань
    else if (i < GRID_SIZE * 3)
        grid[i - (GRID_SIZE * 2)][0] = 0;
    // Нижняя грань
    else
        grid[GRID_SIZE - 1][i - (GRID_SIZE * 3)] = 0;
}	

 

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

Я продолжил возиться. Можно ли еще как-то улучшить код? На самом деле, ответ был ДА. И то, что я в конце концов придумал, было удивительно просто и элегантно, что я, честно говоря, не мог поверить, что мне потребовалось так много времени, чтобы найти его.

Ниже приведена окончательная версия кода. Он имеет один цикл for и ни одного условия. Более того, цикл выполняет только 64 итерации. Это значительно улучшает как сложность, так и эффективность.

for (i = 0; i < GRID_SIZE; ++i) {
    // Верхняя грань
    grid[0][i] = 0;
    
    // Нижняя грань
    grid[GRID_SIZE - 1][i] = 0;
    // Левая грань
    grid[i][0] = 0;
    // Правая грань
    grid[i][GRID_SIZE - 1] = 0;
}	

 

Этот код инициализирует четыре разных граничных точки для каждой итерации цикла. Это не сложно. Это очень эффективно. Его легко читать. По сравнению с оригинальной версией и даже второй версией они как ночь и день.

Я был вполне доволен.

Итак, я кодер с хорошим вкусом?

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

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

Другие статьи по теме

Как писать код каждый день на протяжении года

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

13148

Комментарии

BUG!