新手程序员总是把大量的时间用在学习编程语言上,学习语法,技术和开发工具,他们认为如果掌握了这些就是一个优秀的程序员。但是,实际的编程不仅是要精通技术和工具,关键是要对某个特定领域的问题给出解决方案,而且通常要和其他程序员一起合作完成。因此,很重要的一点就是,程序员要用代码准确的表达出自己的思想,从而让其他人明白程序的含义。
编程大师Robert C. Martin在《Clean Code》中说道:“使用注释是为了弥补我们代码表意上的不足。”这句话就意味着如果你的代码需要添加注释,就说明你的代码还不够好。同时,这也说明如果不能在单纯的代码中展示你对问题或算法的理解是非常失败的,你只能依靠一些注释说明你的想法,而不能仅用代码显示。优秀的代码可以让人在没有注释的情况下看懂并理解,好的编程习惯也是让所有必要的信息都在代码中展示出来。
在编程理论中,有一个概念叫做“可以自我描述的源代码”,尤其是在那些有着较松的命名规则的环境下。引入这个概念,就是要增加代码的可读性,使代码更容易被维护和扩展。
下面我会对比一些“好的代码”和“不清晰的代码”。
命名时要展现你意图
如何在代码中命名一直是一个问题,一些程序员总是用简化,短小或编码后的名字,使得只有他们自己才能看懂。看一些例子:
不好的代码:int d; //elapsed time in daysint ds;int dsm;int faid;
名字中“d”可以表示任何东西,程序员只能用注释来表明他的意图,不能仅用代码显示。而“faid”很容易导致误解为ID。
清晰的代码:
int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;
避免会被误解的命名
导致误解的信息比没有信息更糟糕,有些程序员喜欢“隐藏”一些重要信息,但更糟的是,他们有时会写出一些让人误解的代码。
不好的代码:Customer[] customerList;Table theTable;
变量“customerList”其实不是个list。它只是一个普通的数组(集合)。第二行中,“theTable”是一个Table类的对象,“the”这个词是个不必要的干扰。
清晰的代码:Customer[] customers;Table customers;
适合的名字长度
在现代的编程语言中,很长的变量名字是被允许的,你可以基本不受限制的去命名,但是这样会导致命名的混乱。
不好的代码:var theCustomersListWithAllCustomersIncludedWithoutFiler;var list;
好的名字应该包含足够的单词来表达意思,但是任何不必要的词都会使名字变长,变得难以理解。名称越短越好,前提是能在上下文中表达完整的意思。
清晰的代码:var allCustomers;var customersInOrder;
符合“代码规范”,可以更好的帮助理解
所有的编程语言都有自己的“风格”,叫做表示法。程序员应该写出符合这种表示法的代码,因为其他的程序员也知道这点,并按这种风格写程序。我们来看一个不符合表示法的不好的代码例子。下面的这段代码没有遵循任何代码标准(比如PascalCase, camelCase, Hungarian规范)。更糟糕的是,这里有一个无意义的bool型变量change,这是个描述动作的动词,但这里的bool值应该表示一种状态,所以这个变量应该用一个形容词来命名。
不好的代码:
const int maxcount = 1;
bool change = true;public interface Repository;
private string Name;
public class personaddress;void getallorders();
因为代码规范,当你只看一部分代码时,你就可以理解这里面的变量类型和含义,比如,你看到一个变量“_name”,你就可以知道这是当前类中的一个私有变量。所以,你写出的任何代码都要遵从规范。
清晰的代码:
const int MAXCOUNT = 1;
bool isChanged = true;public interface IRepository;
private string _name;
public class PersonAddress;void GetAllOrders();
一个概念不要用多种词表示,一个词也不要表示多种概念
定义场景中的概念很难,在软件开发过程中,程序员需要花费很多时间去分析某一场景,并命名场景中的各种元素,这样的工作永远都是让程序员头疼的事情。
不好的代码:
//1.
void LoadSingleData();
void FetchDataFiltered();
void GeteAllData();//2.
void SetDataToView();
void SetObjectValue(int value);
在第一段代码中,这个程序员想表达“获取数据”这个概念,但他用了很多不同的词”load”,”fetch”,”get”。在一个场景下,应该用一个统一的词表示这个概念。在第二段代码中,”set”一词被用作了两个概念,第一个是“取出数据显示”,第二个是“为一个对象赋值”,应该用不同的词表示这两个不同的概念。
清晰的代码:
//1.
void GetSingleData();
void GetDataFiltered();
void GetAllData();//2.
void LoadDataToView();
void SetObjectValue(int value);
使用某一领域背景中有意义的名字
程序员写的所有代码都是和某一领域背景相关的,为了让写出的代码可以让个更多的人理解,最好使用该领域背景下的名字。
不好的代码:
public class EntitiesRelation
{
Entity o1;Entity o2;
}
当你在编写针对某个领域的代码时,你应该使用领域背景相关的名字。如果以后有另外的人(不仅是程序员,也许是测试人员)接触你的代码时,他能轻松的理解你与背景相关的代码。所以,程序员首先应该考虑的是领域背景问题,之后才是如何得出解决方案。
清晰的代码:
public class ProductWithCategory
{
Entity product;Entity category;
}
使用上下文环境下有意义的名字
代码里的名字都有自己的上下文,上下文对于理解一个代码是很重要的,因为它能提供额外的信息。我们来看一个典型的“地址”上下文:
不好的代码:
string addressCity;
string addressHomeNumber;
string addressPostCode;
在大多数情况中,“邮政编码”(PostCode)是地址的一部分,很显然,邮政编码不能单独使用(除非你是在开发一个专门处理邮编的应用)。所以,没有必要在“PostCode”的前面加上“address”。而且,所有的这些信息都应该有一个上下文环境,在面向对象编程中,这里应该用一个“Address”类来表达这个地址信息。
清晰的代码:
class Address
{
string city;
string homeNumber;
string postcode;
}
总结
总结一下,作为一名程序员,你应该:
1.起的名字是有意义的,可以表达一个概念;
2.要考虑名字的长度,名称中只有必要的信息;
3.符合“编码规范”,帮助理解;
4.一个概念不要多种名字混用;
5.使用在背景领域和上下文中都有意义的名字。