Warning
This page is located in archive.

Kódovací styl (hodnocený u HW02, HW04, HW06)

Obecně nestačí, aby kód dělal jen to co má, ale musí také být čitelný a udržitelný. Během vývoje jsou zdrojové kódy upravovány mnoha různými lidmi, kteří se ke kódu mohou vrátit třeba po mnoha týdnech. V takovém případě programátor musí celý kód přečíst a znovu jej pochopit (jinak se něco může pokazit pokazí). Je tedy zapotřebí programovat tak, aby (znovu)pochopení kódu bylo co nejrychlejší. Poté, co programátor pochopí kód, tak na něm může provádět údržbu (např. oprava bugů nebo přidání nové funkčnosti). Úprava spaghetti kódu trvá dlouho, protože změna části kódu si často vyžaduje změnu dalších částí (v nejhorším případě programátor přepisuje vše), a čím více změn tím více se musí kód testovat a ladit. Proto je dobré psát kód s průhlednou logikou rozdělenou do jednoduchých funkcí, aby jeho případná úprava vyžadovala minimální změny. Psaním čitelného a udržitelného je ideální začít co nejdříve a to například podle následujících tří základních pravidel:

  1. Konzistentní formát kódu (odsazení, názvy, mezery) zjednodušuje čtení. (lze použít formátování zdrojového kódu)
  2. Účel proměnné/funkce by měl být zřejmý hned od deklarace sebepopisným názvem nebo komentářem. (viz níže Čitelnost kódu)
  3. Dobrá struktura kódu (rozdělení do funkcí, přehledná logika, použití proměnných krátce po deklaraci) umožňuje rychlé pochopení a snadnou úpravu kódu. (viz níže Struktura kódu)

Pokračovat lze studiem:

Čitelnost kódu

  • Pro názvy proměnných, funkcí a komentáře používat výhradně ASCII znaky. Preferovat angličtinu nad češtinou.
  • Účel proměnné/funkce by měl být zřejmý hned od deklarace. Toho dosáhneme sebepopisným názvem, nebo, pokud by název byl příliš dlouhý, komentářem umístěný u dané funkce či proměnné. Toto se však NEvztahuje na krátkodobé proměnné, jako například notoricky známé “i” ve for-cyklu, nebo “tmp” pro krátkodobé uložení dočasné hodnoty.
Špatně! Bez komentáře nelze poznat co funkce dělá. Názvy “foo”, “nbr” a “nbr2” mohou být nesrozumitelné. Název “character” by mohl být deskriptivnější zatímco “text_which_will_be_displayed_on_terminal” je příliš dlouhý.

int foo(int nbr, int nbr2, char character)
{
    int y;
    char *text_which_will_be_displayed_on_terminal;
 
    ...
    return y;
}

Správně :-D Účel funkce a proměnných je nyní zřejmý.

/*
 * Returns result of binary operation applied on given numbers.
 * "number_1" "operator" "number_2" = y
 * 
 * number_1: number on the left side of the operator.
 * number_2: number on the right side of the operator.
 * operator: characters +,-,*, or /, if other character is given the + is set.
 * return: result of the binary operation
 */
int binary_operation(int number_1, int number_2, char operator)
{
    int y;
 
    /* Text which will be displayed on the terminal. */
    char *log;
 
    ...
    return y;
}

  • Kouzelná čísla je lepší dávat do vhodně pojmenovaných konstant.
  • V kontextu ukazatelů preferuj NULL nad 0 (zejména v Céčku).
  • Vždy je potřeba zkontrolovat úspěšnost alokace.
Špatně! Není jasné co 10123 vlastně je a zda se mají oba výskyty upravovat společně. Zároveň volání malloc není kontrolované.

int* sequence = malloc(sizeof(int) * 10123);
...
for (int i = 0; i < 10123; ++i) {
    sequence[i] = i*2;
}

Správně :-D Úpravu velikosti pole lze nyní dělat na jednom místě a promenna “sequence” je kontrolovaná.

#define ARRAY_SIZE 10123
...)
int* sequence = malloc(sizeof(int) * ARRAY_SIZE);
...
if (sequence == NULL) {
    printf("ERROR during memory allocation!");
    return;
}
...
for (int i = 0; i < ARRAY_SIZE; ++i) {
    sequence[i] = i*2;
}

  • Na jednom řádku by mělo být maximálně 80 znaků (včetně komentářů).
Špatně!

/*
 * Creates structure Dog, which contains age, race, and year of birth. Insurance and address are not required (then set it as NULL).
 */
struct Dog* create_dog(int age, const char *race, int birth_year, const char *insurance, const char *address);

Správně :-D Kód lze přečíst bez scrollování na většině monitorů.

/*
 * Creates structure Dog, which contains age, race, and year of birth.
 * Insurance and address are not required (then set it as NULL).
 */
struct Dog* create_dog(int age, 
                      const char *race, 
                      int birth_year, 
                      const char *insurance, 
                      const char *address);

Struktura kódu

  • V kódu by měly být preferovány krátké funkce, které dělají jednu věc, nad dlouhými funkcemi, každá provádějící mnoho věcí (extrém je nacpat vše do main()).
  • Přílišné zanoření IFů a cyklů (např. víc než čtyři zanoření) je znakem špatného strukturování a je výhodnější se pokusit takovému případu vyhnout použitím funkcí.
Špatně! Tři zbytečně zanořené cykly.

void main(void) {
    if (/* condition 1 */) {
        ...
        while (/* condition 2 */) { /* draws multiple rectangles */
            for (int j = 0; j < n; j++) { /* draws a rectangle */
                for (int k = 0; k < n; k++) { /* draws a row */
                    printf(".");
                }
                printf("\n");
            }
            printf("\n\n");
        }
    }
}

Správně :-D Rozděleno do krátkých funkcí. Signatura funkce je sebepopisná, stačí tedy zkrácený komentář.

/* Draws line of dots into cosnole. */
void draw_row(int row_length) {
    for (int k = 0; k < row_length; k++) {
        printf(".");
    }
}
 
/* Draws square from dots into console. */
void draw_square(int edge_length) {
    for (int j = 0; j < edge_length; j++) {
        draw_row(n);
        printf("\n");
    }
}
 
void main(void) {
    if (/* condition 1 */) {
        ...
        while (/* condition 2 */) { /* draws multiple squares */
            draw_square(n);
            printf("\n\n");
        }
    }
}

  • Proměnné by měly být deklarovány/definovány blízko u svého místa použití a její scope omezen.
Špatně! Proměnné “i” a “tmp” přežijou svůj zamýšlený scope. Proměnná “i” může být před cyklem náhodou modifikovaná.

int i = 0, tmp;
int array[8] = {1, 2, 3, 4, 5, 6, 7, 8};
// array modifications
... 
for (i; i < 4; ++i) {
    tmp = array[i];
    array[i] = array[7 - i]
    array[7 - i] = tmp;
}
// printing array
...

Správně :-D Je zajištěno, že “i” je 0. Proměnné “tmp” a “i” existuji jen po trvání svého scope.

int array[8] = {1, 2, 3, 4, 5, 6, 7};
// array modifications
...
for (int i = 0; i < 4; ++i) {
    int tmp = array[i];
    array[i] = array[7 - i]
    array[7 - i] = tmp;
}
// printing array
...

  • U cyklu by mělo být na první pohled jasné zda vůbec někdy skončí (jsou ale výjimky např. aktivní čekáni). Preferuj FOR nad WHILE.
Špatně! Proměnná “r” na které závisí ukončení cyklu je zahrabaná v kódu

int r = scanf("%d", &i);
while (r > 0) {
    // many rows of code
    ...
    r = scanf("%d", &i);
    // many rows of code
    ...
}

Správně :-D Proměnná “r” je poblíž podmínky ukončení cyklu.

while (scanf("%d", &i)) {
    // many rows of code
    ...
}
 
// alternatively
 
int r = scanf("%d", &i);
if (r == 0) {
    return -1;
}
do {
    //many rows of code
    ...
    r = scanf("%d", &i);
} while(r > 0);

  • Opakující se podobné/stejné sekvence kódu (kopírování kódu) je znakem špatného strukturování. Pokus se sdružit kód nebo ho zobecnit ve funkci.
Špatně! Přidání dalšího pointeru vyžaduje vícenásobnou úpravu kódu. Stejně tak i úprava výpisu chyby.

int main(void) {
    while (/* condition */) {
        ...
        if (error_input()) {
            printf("ERROR!: The user input is wrong.");
            printf("Terminating the program.")
            free(some_pointer1);
            free(some_pointer2);
            free(some_pointer3);
            return -1;
        }
        ...
        if (error_connection()) {
            printf("ERROR!: Unable to connect.");
            printf("Terminating the program.")
            free(some_pointer1);
            free(some_pointer2);
            free(some_pointer3);
            return -1;
        }
        ...
    }
    free(some_pointer1);
    free(some_pointer2);
    free(some_pointer3);
    return 0;
}

Správně :-D Případné přidání pointeru nebo hromadná úprava výpisu je nyní jednodušší.

/* Prints error message. */
void print_error(const char *message)
{
    printf("ERROR!: %s", message);
    printf("Terminating the program.");
}
 
int main(void)
{
    bool error_flag = false;
    while (/* condition */) {
        ...
        if (error_input()) {
            print_error("The user input is wrong.");
            error_flag = true;
            break;
        }
        ...
        if (error_connection()) {
            print_error("Unable to connect.");
            error_flag = true;
            break;
        }
        ...
    }
 
    free(some_pointer1);
    free(some_pointer2);
    free(some_pointer3);
    if (error_flag == true) {
        return -1;
    }
    return 0;
}

courses/b0b36prp/tutorials/coding_style.txt · Last modified: 2019/10/11 12:26 by faiglj