Новые книги

Задачи рефакторинга тесно связанны с задачами написания понятного, удобного кода. Соответственно, если я пишу как следует писать или чего лучше избегать — это к рефакторингу не относится. С одной стороны. Но ведь следуя этим рекомендациям, вы можете пересмотреть свой код и исправить потенциальные ошибки. А вот это уже чистой воды рефакторинг. Поэтому я не буду особенно зацикливаться именно на рефакторинге, а буду рассказывать о хорошем, понятном коде.

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

Примеры я буду приводить на языке Object Pascal. В основном я работаю на нём, пишу на Delphi. Предвидя нападки со стороны поклонников C-подобных языков, скажу два тезиса:
PascalABC.NET — это язык программирования Паскаль нового поколения, включающий классический Паскаль, большинство возможностей языка Delphi, а также ряд собственных расширений. Он реализован на платформе Microsoft.NET и содержит все современные языковые средства: классы, перегрузку операций, интерфейсы, обработку исключений, обобщенные классы и подпрограммы, сборку мусора, лямбда-выражения, средства параллельного программирования.

PascalABC.NET является мультипарадигменным языком: на нем можно программировать в структурном, объектно-ориентированном и функциональном стилях.

PascalABC.NET — это также простая и мощная интегрированная среда разработки, поддерживающая технологию IntelliSense, содержащая средства автоформатирования, встроенный отладчик и встроенный дизайнер форм.

Текстовая обработка.

7.68.

Приведем еще один арифметический вычислитель, использующий классический рекурсивный подход:

    /* Калькулятор на основе рекурсивного грамматического разбора.
     * По мотивам арифметической части программы csh (СиШелл).
     * csh написан Биллом Джоем (Bill Joy).
        : var1 = (x = 1+3) * (y=x + x++)            36
        : s = s + 1                                 ошибка
        : y                                         9
        : s = (1 + 1 << 2) == 1 + (1<<2)            0
        : var1 + 3 + -77                            -38
        : a1 = 3; a2 = (a4=a3 = 2; a1++)+a4+2       8
        : sum(a=2;b=3, a++, a*3-b)                  12
     */
    #include <stdio.h>
    #include <ctype.h>
    #include <setjmp.h>
    typedef enum { NUM, ID, OP, OPEN, CLOSE, UNKNOWN, COMMA, SMC } TokenType;
    char *toknames[] = { "number", "identifier", "operation",
      "open_paren", "close_paren", "unknown", "comma", "semicolon" };
    typedef struct _Token {
            char *token;            /* лексема (слово)     */
            struct _Token *next;    /* ссылка на следующую */
            TokenType type;         /* тип лексемы         */
    } Token;
    extern void *malloc(unsigned); extern char *strchr(char *, char);
    char *strdup(const char *s){
          char *p = (char *)malloc(strlen(s)+1);
          if(p) strcpy(p,s); return p;
    }
    /* Лексический разбор ------------------------------------------*/
    /* Очистить цепочку токенов */
    void freelex(Token **p){
         Token *thisTok = *p;
         while( thisTok ){ Token *nextTok = thisTok->next;
            free((char *) thisTok->token); free((char *) thisTok);
            thisTok = nextTok;
         }
         *p = NULL;
    }
    /* Добавить токен в хвост списка */
    void addtoken(Token **hd, Token **tl, char s[], TokenType t){
         Token *newTok = (Token *) malloc(sizeof(Token));
         newTok->next  = (Token *) NULL;
         newTok->token = strdup(s); newTok->type = t;
         if(*hd == NULL) *hd = *tl = newTok;
         else{  (*tl)->next = newTok; *tl = newTok; }
    }
    /* Разобрать строку в список лексем (токенов) */
    #define opsym(c) ((c) && strchr("+-=!~^|&*/%<>", (c)))
    #define is_alpha(c) (isalpha(c) || (c) == '_')
    #define is_alnum(c) (isalnum(c) || (c) == '_')
    void lex(Token **hd, Token **tl, register char *s){
          char *p, csave; TokenType type;
          while(*s){
              while( isspace(*s)) ++s; p = s;
              if( !*s ) break;
                   if(isdigit (*s)){ type = NUM; while(isdigit (*s))s++; }
              else if(is_alpha(*s)){ type = ID;  while(is_alnum(*s))s++; }
              else if(*s == '('){    type = OPEN;  s++; }
              else if(*s == ')'){    type = CLOSE; s++; }
              else if(*s == ','){    type = COMMA; s++; }
              else if(*s == ';'){    type = SMC;   s++; }
              else if(opsym(*s)){    type = OP;  while(opsym(*s))  s++; }
              else {                 type = UNKNOWN;               s++; }
              csave = *s; *s = '\0'; addtoken(hd, tl, p, type); *s = csave;
          }
    }
    /* Распечатка списка лексем */
    void printlex(char *msg, Token *t){
         if(msg && *msg) printf("%s: ", msg);
         for(; t != NULL; t = t->next)
            printf("%s`%s' ", toknames[(int)t->type], t->token);
         putchar('\n');
    }
    /* Система переменных ----------------------------------------- */
    #define NEXT(v)         *v = (*v)->next
    #define TOKEN(v)        (*v)->token
    #define TYPE(v)         (*v)->type
    #define eq(str1, str2)  (!strcmp(str1, str2))
    jmp_buf breakpoint;
    #define ERR(msg,val) { printf("%s\n", msg);longjmp(breakpoint, val+1);}
    typedef struct {
         char *name;        /* Имя переменной      */
         int value;         /* Значение переменной */
         int isset;         /* Получила ли значение ? */
    } Var;
    #define MAXV 40
    Var vars[MAXV];
    /* Получить значение переменной */
    int getVar(char *name){ Var *ptr;
       for(ptr=vars; ptr->name; ptr++)
           if(eq(name, ptr->name)){
              if(ptr->isset) return ptr->value;
              printf("%s: ", name); ERR("variable is unbound yet", 0);
           }
       printf("%s: ", name); ERR("undefined variable", 0);
    }
    /* Создать новую переменную       */
    Var *internVar(char *name){ Var *ptr;
       for(ptr=vars; ptr->name; ptr++)
           if(eq(name, ptr->name)) return ptr;
       ptr->name = strdup(name);
       ptr->isset = 0; ptr->value = 0; return ptr;
    }
    /* Установить значение переменной */
    void setVar(Var *ptr, int val){ ptr->isset = 1; ptr->value = val; }
    /* Распечатать значения переменных */
    void printVars(){ Var *ptr;
         for(ptr=vars; ptr->name; ++ptr)
             printf("\t%s %s %d\n", ptr->isset ? "BOUND  ":"UNBOUND",
                          ptr->name, ptr->value);
    }
    /* Синтаксический разбор и одновременное вычисление ----------- */
    /* Вычисление встроенных функций */
    int apply(char *name, int args[], int nargs){
        if(eq(name, "power2")){
            if(nargs != 1) ERR("power2: wrong argument count", 0);
            return (1 << args[0]);
        } else if(eq(name, "min")){
            if(nargs != 2) ERR("min: wrong argument count", 0);
            return (args[0] < args[1] ? args[0] : args[1]);
        } else if(eq(name, "max")){
            if(nargs != 2) ERR("max: wrong argument count", 0);
            return (args[0] < args[1] ? args[1] : args[0]);
        } else if(eq(name, "sum")){ register i, sum;
            for(i=0, sum=0; i < nargs; sum += args[i++]);
            return sum;
        } else if(eq(name, "rand")){
            switch(nargs){
            case 0:  return rand();
            case 1:  return rand() % args[0];
            case 2:  return args[0] + rand() % (args[1] - args[0] + 1);
            default: ERR("rand: wrong argument count", 0);
            }
        }
        ERR("Unknown function", args[0]);
    }
    /* Вычислить выражение из списка лексем.        */
    /* Синтаксис задан праворекурсивной грамматикой */
    int expr(Token *t){ int val = 0;
        if(val = setjmp(breakpoint)) return val - 1;
        val = expression(&t);
        if(t){ printlex(NULL, t); ERR("Extra tokens", val); }
        return val;
    }
    /* <EXPRESSION> =   <EXPASS>  |
                        <EXPASS>  ";" <EXPRESSION>          */
    int expression(Token **v){ int arg = expass(v);
        if(*v && TYPE(v) == SMC ){
            NEXT(v); return expression(v);
        } else return arg;
    }
    /* <EXPASS> =       <ПЕРЕМЕННАЯ> "=" <EXPASS> |
                        <EXP0>                              */
    int expass(Token **v){ int arg;
        if(*v && (*v)->next && (*v)->next->type == OP &&
           eq((*v)->next->token, "=")){ Var *ptr;
               /* присваивание (assignment) */
               if( TYPE(v) != ID ) /* слева нужна переменная */
                    ERR("Lvalue needed", 0);
               ptr = internVar(TOKEN(v));
               NEXT(v); NEXT(v); setVar(ptr, arg = expass(v)); return arg;
        }
        return exp0(v);
    }
    /* <EXP0>  =  <EXP1>  |   <EXP1> "||" <EXP0>  */
    int exp0(Token **v){ int arg = exp1(v);
        if(*v && TYPE(v) == OP && eq(TOKEN(v), "||")){
              NEXT(v); return(exp0(v) || arg );
              /* помещаем arg ВТОРЫМ, чтобы второй операнд вычислялся
               * ВСЕГДА (иначе не будет исчерпан список токенов и
               * возникнет ошибка в expr(); Это не совсем по правилам Си.
               */
        } else return arg;
    }
    /* <EXP1>  =  <EXP2>  |   <EXP2> "&&" <EXP1>     */
    int exp1(Token **v){ int arg = exp2(v);
        if(*v && TYPE(v) == OP && eq(TOKEN(v), "&&")){
              NEXT(v); return(exp1(v) && arg);
        } else return arg;
    }
    /* <EXP2>  =  <EXP2A>  |   <EXP2A> "|" <EXP2>    */
    int exp2(Token **v){ int arg = exp2a(v);
        if(*v && TYPE(v) == OP && eq(TOKEN(v), "|")){
              NEXT(v); return( arg | exp2(v));
        } else return arg;
    }
    /* <EXP2A>  =  <EXP2B>  |   <EXP2B> "^" <EXP2A>  */
    int exp2a(Token **v){ int arg = exp2b(v);
        if(*v && TYPE(v) == OP && eq(TOKEN(v), "^")){
              NEXT(v); return( arg ^ exp2a(v));
        } else return arg;
    }
    /* <EXP2B>  =  <EXP2C>  |   <EXP2C> "&" <EXP2B>  */
    int exp2b(Token **v){ int arg = exp2c(v);
        if(*v && TYPE(v) == OP && eq(TOKEN(v), "&")){
              NEXT(v); return( arg & exp2b(v));
        } else return arg;
    }
    /* <EXP2C>  =  <EXP3>  |   <EXP3> "==" <EXP3>
                           |   <EXP3> "!=" <EXP3>    */
    int exp2c(Token **v){ int arg = exp3(v);
               if(*v && TYPE(v) == OP && eq(TOKEN(v), "==")){
               NEXT(v); return( arg == exp3(v));
        } else if(*v && TYPE(v) == OP && eq(TOKEN(v), "!=")){
               NEXT(v); return( arg != exp3(v));
        } else return arg;
    }
    /* <EXP3>  =  <EXP3A>  |   <EXP3A> ">"  <EXP3>
                           |   <EXP3A> "<"  <EXP3>
                           |   <EXP3A> ">=" <EXP3>
                           |   <EXP3A> "<=" <EXP3>    */
    int exp3(Token **v){ int arg = exp3a(v);
              if(*v && TYPE(v) == OP && eq(TOKEN(v), ">")){
              NEXT(v); return( arg && exp3(v));
        }else if(*v && TYPE(v) == OP && eq(TOKEN(v), "<")){
              NEXT(v); return( arg && exp3(v));
        }else if(*v && TYPE(v) == OP && eq(TOKEN(v), ">=")){
              NEXT(v); return( arg && exp3(v));
        }else if(*v && TYPE(v) == OP && eq(TOKEN(v), "<=")){
              NEXT(v); return( arg && exp3(v));
        } else return arg;
    }
    /* <EXP3A>  =  <EXP4>  |   <EXP4> "<<" <EXP3A>
                           |   <EXP4> ">>" <EXP3A>    */
    int exp3a(Token **v){ int arg = exp4(v);
              if(*v && TYPE(v) == OP && eq(TOKEN(v), "<<")){
              NEXT(v); return( arg << exp3a(v));
        }else if(*v && TYPE(v) == OP && eq(TOKEN(v), ">>")){
              NEXT(v); return( arg && exp3a(v));
        } else return arg;
    }
    /* <EXP4>  =  <EXP5>  |   <EXP5> "+" <EXP4>
                          |   <EXP5> "-" <EXP4>       */
    int exp4(Token **v){ int arg = exp5(v);
              if(*v && TYPE(v) == OP && eq(TOKEN(v), "+")){
              NEXT(v); return( arg + exp4(v));
        }else if(*v && TYPE(v) == OP && eq(TOKEN(v), "-")){
              NEXT(v); return( arg - exp4(v));
        } else return arg;
    }
    /* <EXP5>  =  <EXP6>  |   <EXP6> "*" <EXP5>
                          |   <EXP6> "/" <EXP5>
                          |   <EXP6> "%" <EXP5>       */
    int exp5(Token **v){ int arg = exp6(v), arg1;
              if(*v && TYPE(v) == OP && eq(TOKEN(v), "*")){
              NEXT(v); return( arg * exp5(v));
        }else if(*v && TYPE(v) == OP && eq(TOKEN(v), "/")){
              NEXT(v); if((arg1 = exp5(v)) == 0) ERR("Zero divide", arg);
              return( arg / arg1);
        }else if(*v && TYPE(v) == OP && eq(TOKEN(v), "%")){
              NEXT(v); if((arg1 = exp5(v)) == 0) ERR("Zero module", arg);
              return( arg % arg1);
        } else return arg;
    }
    /* <EXP6>  = "!"<EXP6> | "~"<EXP6> | "-"<EXP6>
         | "(" <EXPRESSION> ")"
         |  <ИМЯФУНКЦИИ> "(" [ <EXPRESSION> [ "," <EXPRESSION> ]... ] ")"
         |  <ЧИСЛО>
         |  <CH_ПЕРЕМЕННАЯ>                           */
    int exp6(Token **v){ int arg;
        if( !*v) ERR("Lost token", 0);
        if(TYPE(v) == OP && eq(TOKEN(v), "!")){
            NEXT(v); return !exp6(v);
        }
        if(TYPE(v) == OP && eq(TOKEN(v), "~")){
            NEXT(v); return ~exp6(v);
        }
        if(TYPE(v) == OP && eq(TOKEN(v), "-")){
            NEXT(v); return -exp6(v);    /* унарный минус */
        }
        if(TYPE(v) == OPEN){
            NEXT(v); arg = expression(v);
            if( !*v || TYPE(v) != CLOSE) ERR("Lost ')'", arg);
            NEXT(v); return arg;
        }
        if(TYPE(v) == NUM){  /* изображение числа */
            arg = atoi(TOKEN(v)); NEXT(v); return arg;
        }
        if(TYPE(v) == ID){
            char *name = (*v)->token; int args[20], nargs = 0;
            NEXT(v);
            if(! (*v && TYPE(v) == OPEN)){  /* Переменная */
               return expvar(v, name);
            }
            /* Функция */
            args[0] = 0;
            do{ NEXT(v);
                if( *v && TYPE(v) == CLOSE ) break; /* f() */
                args[nargs++] = expression(v);
            }   while( *v && TYPE(v) == COMMA);
            if(! (*v && TYPE(v) == CLOSE)) ERR("Error in '()'", args[0]);
            NEXT(v);
            return apply(name, args, nargs);
        }
        printlex(TOKEN(v), *v); ERR("Unknown token type", 0);
    }
    /* <CH_ПЕРЕМЕННАЯ>  =   <ПЕРЕМЕННАЯ>      |
                            <ПЕРЕМЕННАЯ> "++" |
                            <ПЕРЕМЕННАЯ> "--"
     Наши операции ++ и -- соответствуют ++x и --x из Си         */
    int expvar(Token **v, char *name){
        int arg = getVar(name); Var *ptr = internVar(name);
        if(*v && TYPE(v) == OP){
          if(eq(TOKEN(v), "++")){ NEXT(v); setVar(ptr, ++arg); return arg; }
          if(eq(TOKEN(v), "--")){ NEXT(v); setVar(ptr, --arg); return arg; }
        }
        return arg;
    }
    /* Головная функция ------------------------------------------- */
    char input[256];
    Token *head, *tail;
    void main(){
        do{ printf(": "); fflush(stdout);
          if( !gets(input)) break;
          if(!*input){ printVars(); continue; }
          if(eq(input, "!!")) ; /* ничего не делать, т.е. повторить */
          else{ if(head) freelex(&head); lex(&head, &tail, input); }
          printf("Result: %d\n", expr(head));
        } while(1); putchar('\n');
    }

7.69.

Напишите программу, выделяющую n-ое поле из каждой строки файла. Поля разделяются двоеточиями. Предусмотрите задание символа-разделителя из аргументов программы. Используйте эту программу для выделения поля "домашний каталог" из файла /etc/passwd. Для выделения очередного поля можно использовать следующую процедуру:

    main(){
       char c, *next, *strchr(); int nfield;
       char *s = "11111:222222222:333333:444444";
       for(nfield=0;;nfield++){
         if(next = strchr(s, ':')){
            c= *next; *next= '\0';
         }
         printf( "Поле #%d: '%s'\n", nfield, s);
            /* можно сделать с полем s что-то еще */
         if(next){ *next= c; s= next+1; continue; }
         else    { break; /* последнее поле */    }
       }
    }

© Copyright А. Богатырев, 1992-95
Си в UNIX

Назад | Содержание | Вперед