编码之道【翻译】

    技术2022-05-11  130

    编码之道 2005-12-30 李欣蔚 翻译 引入

    这篇文章记述了我多年开发所使用的编码风格.我的风格并不具有广泛性.事实上,我不知道还有谁和我编码的怪异风格相近.但是我喜欢它并且想和你们分享(你们真是幸运的家伙!).我在所有语言都使用它,包括:C,C++,JAVA,C#,Python,… 如果你想马上快速地浏览一下此种风格,翻到本页底部,看看源代码到底是如何用我的deWitters风格书写的,你就能马上明白它们是如何好了. 为什么需要编码风格?

    每一位程序员都使用某种编码风格--好的或者糟糕的.一种编码风格能够给予代码一致的外观.它能够让算法更加清晰或者更加复杂.下面是使用可靠的编码风格的2个主要原因: 开发清晰和可读的代码,这能够让阅读它的其它人能够迅速理解它的意思.更加重要的是当你回头看一些一年前写的代码的时候,你不会开始想:”我记不清楚了,这简直是在浪费时间…”当团队协作时,最好是所有人都使用统一的编码风格,这样代码都具有统一的外观.

    因为大多数代码都是我自己开发的,我不需要考虑第二个原因.另外还因为我非常固执,我不会采用其它人的风格.这是为什么我的风格是完全能充分产生清晰可读代码的原因. 基本规则

    我在下面的规则中列举了编码风格的重要的几个方面: 所有代码都应该尽可能的可理解所有代码应该尽可能的可读,除非它和上面的规则相冲突所有代码应该尽可能简单,除非它和上面的规则相冲突 对待上面的规则的最好方式是让所有事情尽可能的简单,除非具有足够的可理解性和可读性.引用爱因斯坦的话: 让所有的事情尽可能的简单,但不要过分简单.

    由于现代编程语言的诞生,编写可理解和阅读的代码变得有可能.完全使用汇编语言的时代已经离我们远去.因此我的编码风格试图尽可能的接近自然语言.你会说读我的代码就像读书一样.这也可能是为什么我很少为我的代码写文档的原因.我几乎从不写文档!我甚至认为写文档是”有害的”的(并且写的也不酷)只有当我写些古怪东西的时候我才用注释解释为什么.在下认为,注释应该从不解释你的代码做什么;而应该让你的代码说它自己是做什么的. 任何傻瓜都能写计算机能够理解的代码.优秀的程序员才能写人能够理解的代码. 马丁 弗诺尔 标识符

    让我们以编码风格中最重要的主题--标识符—作为开始.所有标识符和你的其它代码以及注释都应该用英语来书写.软件项目从一个人传给另一个人,从一家公司传给世界另一端的另一家公司是非常寻常的.正因为你并不知道你的代码会传给谁,所以将它们全部用英语书写. 变量

    变量命名应该全部用小写字母并用下划线分隔单词.它更符合书写习惯并因为它最具有可读性.下划线恰好代替了在书写习惯中的空格.相信我,一个叫做”RedPushButton”的并不能像”red_push_button”一样具有容易并快速的阅读.

    如果你想让变量具有可理解性,你必须给它们取明显的名字.非常清楚的是变量都是表示某些”对象”或者”值”的,因此为它们命名.但不要在诸如kuidsPrefixing vndskaVariables ncqWith ksldjfTheir nmdsadType上面浪费时间,因为它们不可读也不清晰.如果你有一个变量”age”,它很明显是int或者unsigned short.如果它是”filename”,很明显它必须是字符串.简单!某些时候对于某些变量,如果你在它的命名中包含类型将具有更好的可读性.比如对于GUI按钮:”play_button”或者”cancel_button”.

    下面是某些能够增加变量可读性的前缀和后缀.下面列出了非常常用的一些:   is_,has_        对于所有boolean值使用这些前缀,这样就不会在类型上出错.它同样对 于if语句非常适合. the_        对于所有的全局变量使用”the_”,它能够非常清楚的表示这里只有一 个. _count        所以_count表示元素的个数.不要使用复数形式”bullets”代替”bullet_count”,因为复数将表示数组.

    数组或者表示列表的其它变量必须写成复数形式,比如enemies, walls 和 weapons.尽管如此,你不需要对所有数组类型使用复数,因为某些数组并不真正表示项目序列.比如”char filename[64]”后者”byte buffer[128]”.

    Const 或者Final

    Const或者final必须用大写形式表示,并使它们更加具有可读性.比如MAX_CHILDREN,X_PADDING或者PI.这种用法很广泛并且应该被用来避免和普通变量相混淆. 你能够在常量名字中使用MAX和MIN表示极限值. 类型

    类型定义了变量的分类.它是有点抽象,所以我不能够使用英语作为如何书写它们的参考.但是我们应该明确区分它们和其它标识符之间的差别.所以对于类型,我使用UpperCamelCase.对于每一个class,struct,enum或者其它在你的变量声明之前的事物都使用UpperCamelCase.

    以此种方式命名你的类型能够让你对普通变量使用同样的名字,比如

        HelpWindow help_window;

        FileHeader file_header;

        Weapon weapons[ MAX_WEAPONS ];

    程序流程 if, else if, else

    书写if语句有多种方式.让我们从圆括号开始.这里有3种主要的放置你的圆括号的方式:     if(condition)       if (condition)       if( condition )   我从来没有在英语正文中看到圆括号像例1一样放置的,所以为什么我要像那样编码呢?哪些单词恰好被不适当的分隔了.第二个例子将条件和圆括号放在一起代替了if语句,同时圆括号居然是if语句的一部分而不是条件语句的一部分.只有最后一个例子有优点,它具有更好的圆括号结构的一个概貌.

        if (!((age > 12) && (age < 18)))

     

        if( !((age > 12) && (age < 18)) )

    就个人而言,我本应该以不同方式写这段代码,但它只是作为一个示例. 现在对于花括号应该怎么办呢?不要使用它们!不幸的是C,C++,JAVA或者C#不允许这样做,只有Python允许.所以我们不能丢掉它们,但我们能做什么能够让它看起来像Python程序一样简洁呢?

        if( condition ) {

            statements;     }

        else if( condition ) {

            statements;     }     else {         statements;     }

    当条件过长,你必须将它们断行.试着在操作符之前将它们断开,并且条件保持最低的关联.在下一行与前面对齐并使用缩排来展示嵌套结构.不要把花括号正好写在条件的后面,在这种情况下将它们紧挨下一行使子块清晰:

        if( (current_mayor_version < MIN_MAYOR_VERSION)

            || (current_mayor_version == MIN_MAYOR_VERSION

                && current_minor_version < MIN_MINOR_VERSION) )

        {         update();     }

    当if条件只有一条语句的时候,你可以不使用花括号,但是要确保你将语句写在下一行,除了它是一条return语句或者break语句.     if( bullet_count == 0 )         reload();       if( a < 0 ) return; while

    While循环与if结构书写一样.我为每一个缩进使用4个空格     while( condition ) {         statements;     }

    对于do-while循环,将while与紧邻的花括号放在相同一行.如果在子块的开始或者结尾的while就不会有混淆.     do {         statements;     } while( condition ) for

    for循环一生有且仅有的意图就是迭代.这就是它要做的!for循环常常能够被while循环代替,但是请不要这样做.当你对某些元素进行迭代的使用,试着使用’for’,只有当它不能解决问题的时候,才使用’while’.’for’结构非常直观:

        for( int i = 0; i < MAX_ELEMENTS; i++ ) {

            statements;     }

    使用I,j,k,l,m作为迭代数字,’it’作为对对象的迭代. switch

    Switch语句有与if和while结构相似的结构.唯一需要考虑的就是额外的标识符.同样在break后面留出多余的空格.     switch( variable ) {         case 0:             statements;             break;           case 1:             statements;             break;           default:             break;     } Functions 函数

    函数是用来干事儿的,它们的名字应该清晰.因此,通常都包含一个动词,没有例外!使用与变量相同的命名方式,这意味所有小写单词由下划线分隔开.这允许你在你的代码中使用小段句子以便让每个人都理解.

    同样确保函数做的事情与它的名字相符,不要过多,也不要太少.所以如果由一个函数叫做”load_resources”,确信它只是装载资源而不会去初始化填充.某些时候,你图方便就把初始化的事情放在load_resources中,因为你在更高层已经调用它,但是这将在以后引起问题.我的deWiTTERS 风格使用非常少的注释,所以一个函数应该明确的展示它的名字叫它做的事情.并且当一个函数返回某些东西,确信它的名字清晰的反映它将返回什么.

    某些函数以”阴和阳”对的形式出现,你应该统一你的命名方式.比如: get/set, add/remove, insert/delete, create/destroy, start/stop, increment/decrement, new/old, begin/end, first/last, up/down, next/prev, open/close, load/save, show/hide, enable/disable, resume/suspend等等.

    下面是一个简单的函数调用.在开始的圆括号的后面以及末尾的圆括号前面使用空格,就像if结构一样.同样在都好后面空格,就像使用英语一样.

        do_something( with, these, parameters );

    当函数调用太长的时候,你应该将它们断开为几行.将下一行与前面对齐,这样结构非常明显,并以都好断开.

        HWND hwnd = CreateWindow( "MyWin32App", "Cool application",

                                  WS_OVERLAPPEDWINDOW,

                                  my_x_pos, my_y_pos,

                                  my_width, my_height

                                  NULL, NULL, hInstance, NULL );

    定义 下面是函数定义的例子:

        bool do_something_today( with, these, parameters ) {

            get_up();         go_to( work, car );         work();         go_to( home, car );         sleep();           return true;     } 确保你的代码不会太长,或者按照linus的说法是:函数的最长长度与函数的复杂性以及缩进层次成反比”.所以,如果你有一个概念性的简单函数,它是一个长(但是简单)case语句,你就必须对许多不同的case做许多不同的小事情.尽管如此,如果你有一个复杂的函数,并且你怀疑一个天赋不佳的一年级高中生都不知道函数是什么,你随时应该坚持最大限制.使用描述命名的辅助函数(如果考虑到注重性能的情况,你能够让编译器去inline它们,并且它会比你做的更好.) 类

    对于class的命名方式我使用和类型一样的UpperCamelCase.不要为每个class都加上’C’前缀,那只是在浪费字节和时间而已.

    对于任何事情,给class清晰并且明显的名字.如果一个class是”Window”类的子类,将它命名为”MainWindow”.

    当创建一个新的class,记住任何事情都来自数据结构.

    数据支配.如果你已经选择了正确的数据结构并将事情组织得当,算法将通常是不证自明的.数据结构而不是算法是编程的中心. Fred Brooks   继承

    “is a”关系应该使用继承.”has a”应该使用包含.确保不要过分使用继承.它本身是伟大的技术,但只有在被适当应用的情况. 成员

    你应该明确成员和普通变量之间的差异.如果你不这样做.你将在以后后悔.某些情况下将它们命名为m_Member或者fMember.我喜欢使用对静态成员使用my_member,对静态普通变量使用our_member.这将在下面语句中非常不错:

        if( my_help_button.is_pressed() ) {

            our_screen_manager.go_to( HELP_SCREEN );

        }

    应用于变量命名的其它规则同样能够可用于成员.这里有一个问题我不能解决,那就是boolean成员.记住在boolean值中必须有”is”和”has”.当于”my_”结合的时候,你得到疯狂的名字,比如”my_is_old”和”my_has_children”.我也没有找到此问题的完美解决方案,所以如果你有任何建议,请发EMAIL给我.

    你不应该将class的成员声明为public.某些时候它看起来能更快速的实现,并因此更好,但是你错了(我也曾经历过).你应该使用public的方法来取得class的成员. 方法

    应用到函数的任何事物都能够应用在方法上,记住名字中要包含动词.确保在你的方法名字中不要包含class的名字. 代码结构 将相似的行对齐,能够让你的代码看起来更加统一:     int object_verts[6][3] = {

            {-100,  0, 100}, {100, 0,  100}, { 100, 0, -100},

            {-100, 11, 100}, (100, 0, -100}, {-100, 0, -100}

        };       RECT rect;

        rect.left   = x;

        rect.top    = y;

        rect.right  = rect.left  + width;

        rect.bottom = rect.right + height;

    千万不要将多行写在一行上面,除非你对此有好的理由.其它原因是相似的行应该为声明放每行一句:

        if( x & 0xff00 ) { exp -= 16; x >>= 16; }

        if( x & 0x00f0 ) { exp -=  4; x >>=  4; }

        if( x & 0x000c ) { exp -=  2; x >>=  2; }

    同种类型的相关变量能够以相同语句声明.这样使代码更加紧凑,并更加统一.但是不要将不相干的变量放在同一行.     int x, y;     int length; 命名控件,包

    命名空间或者包应该用小写,而且不用任何下划线.为你写的每一个模块或者层使用命名空间,这样在代码中不同的层更加清晰. 设计

    当我开始项目的时候我并不做太多前端设计.我只要在我的脑海中有一个全局结构就开始编写代码.代码进化—无论你喜欢还是不喜欢—给代码进化的机会.

    进化的代码因为着重写糟糕的代码,并且在某些编码后你的代码将变糟糕.我使用下面的原则来保持代码中的良好结构. 当函数太长,将它划分为一些更小的辅助函数.如果一个类包含太多的成员和方法,将这个类划分为辅助类并在主类中包含辅助类(不要在这里使用继承!)确保辅助类不会引用或者由于任何原因引用主类当模块包含太多的类,将它划分为更多的模块,并且高层模块使用底层模块.当你实现了功能或者修改了bug,通读一遍你改变的整个文件,并确保所有事物都处于非常完美的状态.

    某些项目可能变大,非常大.处理这种增长的复杂性的方法是将你的项目分为不通的层.实践中,层作为命名空间实现的.底层被高层所使用.所以每一层为上一层提供功能.最高层为用户提供功能. Files 文件

    文件应该按照它包含的类的名字来命名.不要在一个文件中放多个class,这样在你搜索某个类的时候你才知道在那里去找.目录结构应该表示命名空间. .h 文件结构

    C或者C++头文件显示了实现的接口.这是在设计a.h文件时候的关键知识.在一个class中,首先定义能够被其它类使用的”public”接口,然后定义所有”protected”方法和成员.人们使用类的非常重要的信息是使用首先显示出来的方法.我不使用private方法和成员,这样所有成员组织在class声明的底部.这样你能够能够快事看到类底部的内容.将方法以它们的意思进行分组.     /*      * license header      */       #ifndef NAMESPACE_FILENAME_H     #define NAMESPACE_FILENAME_H       #include <std>       #include "others.h"         namespace dewitters {           class SomeClass : public Parent {             public:                 Constructor();                 ~Destructor();                   void public_methods();                     protected:                 void protected_methods();                   int     my_fist_member;                 double  my_second_member;                   const static int MAX_WIDTH;         };           extern SomeClass the_some_class;     }       #endif .java .cs 文件结构

    .java 或者 .cs文件并不提供class的界面,它们只包含实现.因为数据结构比算法更加重要,所以在方法之前定义成员.当浏览代码的时候,你能够得到关于此class的数据成员的印象.相似的代码应该组织在一起. Here follows a sketchy overview of a .java or .cs file: 下面显示了对.java或者.cs文件的一个粗略概览:     /*

         * license header

         */

        package com.dewitters.example;

     

        import standard.modules.*;

     

        import custom.modules.*;

       

        class SomeClass extends Parent {

            public final int MAX_WIDTH = 100;

     

            protected int     my_first_member;

            protected double  my_second_member;

     

            Constructor() {

            }           Methods() {         }     }   笑话

    某些人喜欢在他们的代码中放些小笑话,而其它人憎恨这类搞笑分子.以我来看只要不影响代码的可读性和程序的执行,你可以随便使用笑话. deWiTTERS Style vs. others

    这里我将给你看些活生生的代码.我偷了些别人的代码,并以我自己的方式重写.你可以自己判断一下我的风格到底是好是坏.在我看来,你能够快速的阅读我的代码,因为它们更断,并且所有标识符都被谨慎的命名. 如果你认为你已看到过能击败我的风格的代码,请给我写EMAIL,并且我将会把它写进更强的’deWiTTER’风格中,并发布在这里. Indian Hill C Style /* *      skyblue() * *      Determine if the sky is blue. */   int                      /* TRUE or FALSE */ skyblue()  {        extern int hour;         if (hour < MORNING || hour > EVENING)               return(FALSE); /* black */        else               return(TRUE);  /* blue */}   /* *      tail(nodep) * *      Find the last element in the linked list *      pointed to by nodep and return a pointer to it. */  NODE  *                   /* pointer to tail of list */ tail(nodep) NODE  * nodep;            /* pointer to head of list */   {        register NODE *np;     /* current pointer advances to NULL */        register NODE *lp;     /* last pointer follows np */         np = lp = nodep;        while ((np = np->next) != NULL)               lp = np;        return(lp);}   Rewritten to deWiTTERS Style: bool  sky_is_blue()  {    return the_current_hour >= MORNING && the_current_hour <= EVENING;}  Node *  get_tail( Node *  head )  {    Node* tail;    tail = NULL;     Node* it;    for( it = head; it != NULL; it = it->next ) {        tail = it;    }     return tail;}   "Commenting Code" from Ryan Campbell /* * Summary:     Determine order of attacks, and process each battle * Parameters:  Creature object representing attacker *              | Creature object representing defender * Return:      Boolean indicating successful fight * Author:      Ryan Campbell */ function beginBattle(attacker, defender)  {    var isAlive;    // Boolean inidicating life or death after attack    var teamCount;  // Loop counter     // Check for pre-emptive strike    if(defender.agility > attacker.agility) {        isAlive = defender.attack(attacker);    }     // Continue original attack if still alive    if(isAlive) {        isAlive = attacker.attack(defender);    }     // See if any of the defenders teammates wish to counter attack    for(teamCount = 0; teamCount < defender.team.length; i++{        var teammate = defender.team[teamCount];        if(teammate.counterAttack = 1{            isAlive = teammate.attack(attacker);        }    }       // TODO: Process the logic that handles attacker or defender deaths     return true;}   //  End beginBattle Rewritten to deWiTTERS Style:function handle_battle( attacker, defender )  {    if( defender.agility > attacker.agility ) {        defender.attack( attacker );    }     if( attacker.is_alive() ) {        attacker.attack( defender );    }     var i;    for( i = 0; i < defender.get_team().length; i++ ) {        var teammate = defender.get_team()[ i ];        if( teammate.has_counterattack() ) {            teammate.attack( attacker );        }    }     // TODO: Process the logic that handles attacker or defender deaths}  

    最新回复(0)