Контакты

Для связи с нами можно использовать:
irc://irc.chatnet.ru:#gfs
icq://546460
email://cobalt[@]gfs-team.ru

Все материалы предоставлены только с ознакомительной целью
ГлавнаяСтатьиКодингКрасота программирования: что такое хорошо, а что такое плохо.
© Sun Inventor 14.05.2007

Введение

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

Переменные

Чем меньше время жизни переменной, тем лучше

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

!Хорошо
        15:return_value = TIMEDOUT;
                ...
        25:return_valie = OK;
!Плохо 
        15:return_value = TIMEDOUT;
                ...
        150:return_valie = OK;
Одна переменная для одной цели

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

!Плохо

        temp = filename[1];
        filename[1] = filename[2];
        filename[2] = temp;
        ...

        temp = port[1];
        port[1] = port[2];
        port[2] = temp;

!Хорошо
        
        old_filename = filename[1];
        filename[1] = filename[2];
        filename[2] = old_filename;
        ...

        old_port = port[1];
        port[1] = port[2];
        port[2] = old_port;

Взглянув на первый пример можно сделать вывод, что переменные directory и filename как-то связанны, что вводит в заблуждение.

C подозрением относитесь к глобальным переменным

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

Используйте директиву #define
!Плохо
        ...
        error = 1234;
        ...
И как понять что обозначает error 1234?

!Хорошо
        ...
        #define CANT_OPEN_FILE 1234
        ...
        error = CANT_OPEN_FILE;
        ...

Ну теперь можно предположить что произошла ошибка открытия файла.

Используя #define мы убили сразу двух зайцев. Во-первых повысили ясность кода а во вторых избавили себя от необходимости лазить по всему исходнику в поисках 1234, если изменится код ошибки.

Обьявляйте строки как MAXLINE + 1

Многие программисты забывают, что такие функции как strcpy, strcat итд не заботятся о добавлении нуль байта в конец строки. Это чревато однобайтовым переполнением и другими ошибками. Объявляя строку как MAXLINE + 1 мы заботимся о нулевом байте.

!Плохо 

        char    buf[MAXLINE];
        
        strncpy(buf, evil_string, MAXLINE); 
!Хорошо

        char    buf[MAXLINE+1];
        
        strncpy(buf, evil_string, MAXLINE);
Чаще пользуйтесь структурами

Допустим у нас есть данные о работниках фирмы - name, address, phone, gender, salary. Имеет смысл объединить их в структуру. Если эти данные передаются в функцию то гораздо удобнее передать саму структуру, чем писать все эти поля.

!хорошо
        ...
        struct  employee {
                char    name[MAXLINE+1];
                char    address[MAXLINE+1];
                char    phone[MAXLINE+1];
                char    gender[MAXLINE+1];
                char    sakary[MAXLINE+1];
        };
        ...

        int     check_work(struct employee *);  
Тщательно выбирайте имена переменных.

Выбирая имя переменной нужно прежде всего позаботиться о том чтобы оно выражало суть этой переменной. Часто хорошим именем является словесное описание значения переменной.

!Плохо
        x = sqrt(b*b - 4ac);

!Хорошо

        discriminant = sqrt(b*b - 4ac); 

Для именования индексов циклов обычно применяются i,j,k. Если переменная являющаяся индексом цикла будет применяться после завершения цикла, следует выбрать более подходящее имя для нее.

!Плохо
        while (i < target_port) {
                ...
                i++;
        }
        ...
        remoute_port = i;       

!Хорошо
        while (current_port < target_port) {
                ...
                current_port++;
        }
        ...
        remoute_port = current_port;
Правильно именуйте флаги.

Не присваивайте переменной статуса имя flag. Выбирайте яркие имена для флагов.

!Плохо

        if (sock == CONNECT_FLAG) {
                ...
        }
        /* Здесь непонятно что значит CONNECT_FLAG. Произошло соединение или
        нет? */

        if (sock == ERROR_ONE_FLAG) {
                ...
        }
        if (sock == ERROR_TWO_FLAG) {
                ...
        }
        /* Здесь можно догадаться что происходят какие-то ошибки, но вот какие
        остается загадкой.

!Хорошо

        if (sock == CONNECT_OK) {
                ...
        }
        /* Ну вот сейчас уже можно сказать что соединение установлено */
        if (sock == CANT_READ) {
                ...
        /* ошибка чтения */
        }
        if (sock == CANT_WRITE) {
                ...
        }
        /* ошибка записи */
Операторы

Группируйте взаимосвязанные элементы.

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

!Плохо
        25: strncpy(employee.name, current_name, MAXLINE);
        ...
        80: strncpy(employee.name, current_phone, MAXLINE); 
!Хорошо
        25: strncpy(employee.name, current_name, MAXLINE);
        26: strncpy(employee.name, current_phone, MAXLINE);
Размещайте нормальный вариант после if, а не после else
!Плохо
        if (sock == CANT_CONNECT) {
                ...
        } else {
                ...
        }
!Хорошо
        if (sock == CONNECT_OK) {
                ...
        } else {
                ...
        }
Пользуйтесь скобками

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

!Было
        ...
        if( status == OK)
                make_deal();
!Стало
        if( status == OK)
                make_deal();
                check_sock();

!А должно было стать
        if( status == OK) {
                make_deal();
                check_sock();
        }

Когда вы начнете искать ошибку в коде вполне вероятно, что из-за обманчивого форматирования, вы ее не увидите. Таким образом можно cделать вывод:

!Плохо
        for (i = 1; i < MAX_VALUE; i++)
                for(j = 1; j < MAX_VALUE; j++)
                        for(k = 1; k < MAX_VALUE; k++)
                                ...

!Хорошо
        for (i = 1; i < MAX_VALUE; i++) {
                for(j = 1; j < MAX_VALUE; j++) {
                        for(k = 1; k < MAX_VALUE; k++) {
                                ... 
                        }
                }
        }
Избегайте пустых блоков в конструкциях if-else
!Плохо
        if (FILE_OPENED)
                ;
        else {
                /* делаем что-то если файл не открыт */
        }

!Хорошо
        if ( ! FILE_OPENED) {
             /* делаем что-то если файл не открыт */
        }
Упрощайте проверки в операторах if-else

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

!Плохо
        if ( 
                input_character == " " ||
                input_character == "," ||
                input_character == "." ||
                input_character == "!" ||
                input_character == "(" ||
                input_character == ")" ||
                input_character == ":" ||
                input_character == ";" ||
                input_character == "?" ||
                input_character == "-" ||
                ) {

                character_type = character_type_punctuation;
        } 
        else if ( "0" <= input_character && input_character <= "9") {
                character_type = character_type_digit;
        }
        else if ( "a" <= input_character && input_character <= "z" ||
                  "A" <= input_character && input_character <= "Z") {
                character_type = character_type_letter;
        }

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

!Хорошо
        if ( is_punctuation(input_character)) { 
                character_type = character_type_punctuation;
        } 
        else if ( is_digit(input_character)) {
                character_type = character_type_digit;
        }
        else if ( is_letter(input_character)) {
                character_type = character_type_letter;
        }
Используя конструкцию if-else следует выносить вперед варианты, встречающиеся чаще других.

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

!Плохо
        if ( is_punctuation(input_character)) { 
                character_type = character_type_punctuation;
        }        
        else if ( is_digit(input_character)) {
                character_type = character_type_digit;
        }
        else if ( is_letter(input_character)) {
                character_type = character_type_letter;
        }        

!Хорошо

        if ( is_letter(input_character)) {
                character_type = character_type_letter;
        }
        else if ( is_punctuation(input_character)) {    
                character_type = character_type_punctuation;
        } 
        else if ( is_digit(input_character)) {
                character_type = character_type_digit;
        }
Улучшайте читабельность в конструкции switch-case

Этого можно добиться двумя способами. Во первых упорядочив перечисление например по алфавиту. Если обрабатывается много условий то такой подход наверяка упростит поиск необходимого варианта.

!Хорошо
        switch (action) {
                case "a":
                        ...
                        break;          
                case "b":
                        ...
                        break;
                case "c":
                        ...
                        break;

                     ...

                case "y":
                        ...
                        break;
                default:
                        ...
        }
!Плохо
        switch (action) {
                case "c":
                        ...
                        break;          
                case "a":
                        ...
                        break;
                case "e":
                        ...
                        break;

                     ...

                case "y":
                        ...
                        break;
                default
                        ...
        }               
Не изменяйте переменную цикла for в его теле.

Это является плохим тоном.

!Плохо
        for ( i = 1, i < 100; i++) {
                ...
                if (...) {
                        ...
                        i = 100;
                }
        }

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

!Хорошо
        for ( i = 1, i < 100; i++) {
                ...
                if (...) {
                        ...
                        break;
                }
        }
Пытайтесь уменьшить длину цикла.

Это поможет лучше понять логику его выполнения.

!Плохо
        for ( i = 1, i < MAX_VALUE, i++) {
                ...50 строк кода...
                for ( j = 1, j < MAX_VALUE, j++) {
                        ...60 строк кода...
                        for ( k = 1, k < MAX_VALUE, k++) {
                                ...
                        }
                }
        }

Предпочтительнее вынести эти ...n строк кода... в отдельные функции.

!Хорошо
        for ( i = 1, i < MAX_VALUE, i++) {
                one();
                for ( j = 1, j < MAX_VALUE, j++) {
                        two();
                        for ( k = 1, k < MAX_VALUE, k++) {
                                ...
                        }
                }
        }
Стили форматирования

Главной задачей форматирования является сделать код более читабельным. Различие разных стилей в формировании отступов, расстановки скобок, оформлении операторов. Сейчас широкое распространение имеют следующие:
Стиль K&R - назван так потому что именно в таком стиле написаны все примеры к книгам Кернигана и Ричи. Иногда называют kernel style из-за того, что ядро UNIX написано именно в этом стиле.


        if (...) {
        ......../* отступ на таб или 8 пробелов */
        ......../* код */ 
        }

Стиль Олмана - назван так в честь Эрика Олмана, написавшего много утилит для BSD. Еще известен как bsd style.


        if (...)
        {
        ......../* отступ на таб или 8 пробелов */ 
        }

Whitesmith - назван так из-за того, что использовался в ide компилятора whitesmith c.


        if (...)
        ........{  
        ......../* отступ на таб или 8 пробелов */
        ......../* код */
        .........}

Стиль GNU - назван так из-за использования в редакторе Emacs и некоторых других GNU утилит.


        if (...)
        ..{ /* отступ на 2 пробела */ 
        ..../* отступ на 4 пробела */
        ..../* код */
        ..}

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

Заключение

Прежде чем кидаться за реализацию стоит спокойно сесть и набросать примерный план программы. Хорошее планирование будущей архитектуры избавит вас от множества проблем на стадии реализации. Если перед вами стоит задача написать программу из 10 строк кода то врятли вы задумаетесь об именовании переменных, группировке элементов и других методиках, позволяющих упростить код. Он и так прост. Но что вы будите делать если реализация поставленной задачи еле-еле убирается в 50 000 строк?

При написании кода задавайте себе вопрос - сможет ли другой человек понять его? Даже если вы не предполагаете распространение исходников, такой подход значительно упростит вашу задачу, если например спустя пол года вам понадобится вернуться к этой программе. Чем лучше продуманна структура программы, тем проще вам будет. Идеальный вариант - если при написании программы, в определенный момент времени, вы будите думать только об одной маленькой части. В этом случае вам будет безразлично - 10 или 10 000 строк вам нужно написать. Так давайте не будем усложнять себе жизнь.




© Sun Inventor 14.05.2007

e-Commerce Partners Network
mvd_of_russia написал:

Кулл стотья в будушем понадобится.

gjdho64236 написал:

Стоит добавить что это статья для Си. Для С++ если же это применять - это будет полный ахтунг, как для вариант хорошо так и для варианта плохо.

= написал:

Примеры на си, но это в принципе относится ко всем языкам, просто в статье хотят донести до четателя что код надо оптимизировать всегда. И будет легче жить

Гриша написал:

Статья действительно не плохая автору респект

Ник:

Текст:
P Br B I Qute



Код: обновить
Последние комментарии
22.07.2017 20:22:28 ArlenePat написал:
Революционное обновление "XRumer 16.0 + XEvil 3.0": автораспознавание бесплатно и быстро...
Новая статья
22.07.2017 18:50:02 ArlenePat написал:
Нейросетевое обновление "XRumer 16.0 + XEvil 3.0": взлом бесплатно и быстро...
Сайт снова работает!
22.07.2017 17:21:42 ArlenePat написал:
Принципиально новое обновление "XRumer 16.0 + XEvil": автораспознавание бесплатно и быстро...
Релиз
Реклама

Тут должна была быть ваша реклама, но мы потеряли глиняную табличку с ее текстом. SapeId: 665044

Rambler's Top100