刚刚接触IT行业的程序员都会有一个痛点,自己写的代码觉得很好,但是别人看你的代码的时候,总觉得太乱了,没有规范。后面维护的程序员就痛苦了。为什么我会提出这个问题,我就是从这一样走过来。今天推荐看的一本书《代码整洁之道》。
思维导图
总结:
代码是团队沟通方式。
- 设计思想流程图编写。
- 代码结构清晰,注释合理,方法参数备注。
- 遵循团队制定的代码规范,就必须遵循下去,新手进来也要遵循。
有意义的命名
- 使用有意义且可拼写的变量名
#反面
$ymdstr = $moment->format('y-m-d');
#正面
$currentDate = $moment->format('y-m-d');
- 同种类型的变量使用相同词汇
#反面
getUserInfo();
getClientData();
getCustomerRecord();
#正面
getUser();
- 使用易检索的名称
#反面
//What the heck is 86400 for?(86400到底是干什么的)
addExpireAt(86400);
#正面
// Declare them as capitalized `const` globals.(用常量大写来声明变量)
interface DateGlobal {
const SECONDS_IN_A_DAY = 86400;
}
addExpireAt(DateGlobal::SECONDS_IN_A_DAY);
- 使用解释型变量
#反面
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/';
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZipCode($matches[1], $matches[2]); //*对比
#正面
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/';
preg_match($cityZipCodeRegex, $address, $matches);
list(,$city, $zipCode) = $matchers; //list()把数组中的值赋给一组变量
saveCityZipCode($city, $zipCode); //这里只用了 $city 和 $zipCode 变量。
-
避免心理映射
不要让读者在脑中将你的名称译为他们熟知的名称。
-
命名
类名:不应当时动词,避免使用Manager、Processor、Data、Info这样的类名。
方法名:动词或者动词短语,言到意到,意到言到 。eg:getName()
使用领域名称:eg :AccountVisitor =>Visitor模式
别用双关语:避免将同一单词用于不同的目的。
不添加没用的语境: eg:GSDAccountAddress =>Address
函数
- 方法有多短小才合适没有定论,但是长达500行的一个方法,绝对让阅读者起杀人之心。
#反面
// 获取个人信息
Private UserDTO getUserDTO(Integer userId)
{
//获取基本信息
… 此处写了10行
//获取最近的一次订单信息
… 此处写了30行
// 获取钱包余额、可用优惠券张数等
... 此处写了30行
return userDTO;
}
#正面
// 获取个人信息
Private UserDTO getUserDTO(Integer userId)
{
//获取基本信息
UserDTO userDTO= getUserBasicInfo(userId);
//获取最近的一次订单信息
userDTO.setUserLastOrder(getUserLastOrder(userId));
// 获取钱包、可用优惠券张数等
userDTO.setUserAccount(getUserAccount(userId));
return userDTO;
}
Private UserDTO getUserBasicInfo(userId);
Private UserLastOrder getUserLastOrder(userId);
Private UserAccount getUserAccount(userId);
-
单一责任(一个函数只负责一件事情)
-
每一个函数一个抽象层级(阅读代码自顶向下代码:向下规则)
总分的写法(个人理解):总是写步骤,分是写步骤需要的方法。 -
使用描述性的名称(动词+关键词)
eg:writeField
- 减少if/else嵌套,多使用多态,一个函数做一件事,遵从单一责任
#反面
// 修改用户密码,这个例子只有3层嵌套,很温柔了
public boolean modifyPassword(Integer userId, String oldPassword, String newPassword) {
if (userId != null && StringUtils.isNotBlank(newPassword) && SpringUtils.isNotBlank(oldPassword)) {
User user = getUserById(userId);
if(user != null) {
if(user.getPassword().equals(oldPassword) {
return updatePassword(userId, newPassword)
}
}
}
}
#正面
// 修改用户密码
Public Boolean modifyPassword(Integer userId, String oldPassword, String newPassword) {
if (userId == null || StringUtils.isBlank(newPassword) || StringUtils.isBlank(oldPassword)) {
return false;
}
User user = getUserById(userId);
if(user == null) {
return false;
}
if(!user.getPassword().equals(oldPassword) {
return false;
}
return updatePassword(userId, newPassword);
}
- 函数方法参数过多,如何优化。
#反面
Class Book
{
public function create($name, $cateId, $author, $year, $price, $publish, $country, $language)
{
$params = [
'name' => $name,
'cateId' => $cateId,
'author' => $author,
'year' => $year,
'price' => $price,
'publish' => $publish,
'country' => $country,
'language' => $language,
];
}
}
#真面
class BookModel
{
protected $name;
protected $cateId;
protected $author;
protected $year;
protected $price;
protected $publish;
protected $country;
protected $language;
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
public function getCateId()
{
return $this->cateId;
}
public function setCateId($cateId)
{
$this->cateId = $cateId;
}
public function getAuthor()
{
return $this->author;
}
public function setAuthor($author)
{
$this->author = $author;
}
public function getYear()
{
return $this->year;
}
public function setYear($year)
{
$this->year = $year;
}
public function getPrice()
{
return $this->price;
}
public function setPrice($price)
{
$this->price = $price;
}
public function getPublish()
{
return $this->publish;
}
public function setPublish($publish)
{
$this->publish = $publish;
}
public function getCountry()
{
return $this->country;
}
public function getLanguage()
{
return $this->language;
}
public function setLanguage($language)
{
$this->language = $language;
}
}
#控制器调用
Class Book
{
public function create(BookModel $bookModel)
{
$params = [
'name' => $bookModel->getName(),
'cateId' => $bookModel->getCateId(),
'author' => $bookModel->getAuthor(),
'year' => $bookModel->getYear(),
'price' => $bookModel->getPrice(),
'publish' => $bookModel->getPublish(),
'country' => $bookModel->getCountry(),
'language' => $bookModel->getLanguage(),
];
}
}
- 抽离try/catch
1、 使错误处理代码从主路径代码中分离出来得到简化。
2、关键try应该是这个函数的第一个单词且catch/finally后无其他内容。
3、消灭错误码
#反面
// 获取个人信息
Private UserDTO getUserDTO(Integer userId)
{
try {
//获取基本信息
... 此处写了10行
//获取最近的一次订单信息.
...此处写了20行
// 获取钱包、可用优惠券张数等
...此处写了20行
}catch (Exception e) {
logger.error(e);
return null;
}
}
return userDTO;
}
#正面
// 获取个人信息
Private UserDTO getUserDTO(Integer userId)
{
//获取基本信息
UserDTO userDTO= getUserBasicInfo(userId);
//获取最近的一次订单信息
userDTO.setUserLastOrder(getUserLastOrder(userId));
// 获取钱包、可用优惠券张数等
userDTO.setUserAccount(getUserAccount(userId));
return userDTO;
}
Private UserDTO getUserBasicInfo(userId);
Private UserLastOrder getUserLastOrder(userId);
Private UserAccount getUserAccount(userId){
try{ // TODO } catch( Exception e) { //TODO}
}
- 别重复自己
比如:算法重复了多次,还与其他代码混在一起,修改是需要修改4个地方,整体的可读性下降。
-
注释
1、法律信息(版权及著作权声明)
2、提供信息的注释,函数名
3、对意图的解释——某个决定后面的意图
4、阐释
5、警示
6、TODO注释(工作中将需要完成的功能列出来,完成就注释) -
格式
1、垂直格式:用大多数为200行、最长500行的单个文件构造出色的系统是可能的,尽管这并非不可违背的原则,也应该乐于接受。短文通常比长文件易于理解。
1.1、源文件名称简单,程序的结构先总再分,阅读从上往下读。
1.2、每组代码行展示一条完整的思路。这些思路用空白行区隔开。每个空白行都是一条线索,标识出新的独立概念。
1.3、如果说空白行隔开了概念,靠近的代码则暗示了它们之间的紧密关系。所以,关系紧密的代码应该互相靠近。
1.4、方法相关的方法应给放在附近,避免跳来跳去。
2、横向格式:保持代码短小。
2.1、水平方向的间隔和靠近:使用空格字符分隔,不在函数名和左圆括号加空格。
2.2、水平对齐:不是把强调的声明变量拉开距离。
2.3、缩进:突显层次感
2.4、空范围:有时while或for的语句体为空。空范围缩进,用括号包围起来。
2.5、开发前先定好团队规则,缩进,命名类、变量和方法。 -
对象与数据结构
1、在不改动既有数据结构前提下添加新函数,面向对象在不改动既有的函数前提下添加新类
2、得墨忒耳定律是什么?
得墨忒耳定律-对象 O 的 M 方法,可以访问/调用如下的:
1.这个对象自己拥有的方法;
2.传入该方法的参数的方法;
3.该方法创建的对象的方法;
4.该对象直接拥有的对象的方法;
换言之:每个单元(对象或方法)应当对其他单元只拥有有限的了解。
打个比方:假设我在便利店购物。付款时,我是应该将钱包交给收银员,让她打开并取出钱?还是我直接将钱递给她?(不要和陌生人说话。)
class A{
private B b = new B();
private void methodE(){}
public void methodA(C c){
D d = new D();
methodE(); //1这个对象自己拥有的方法,可调用
c.print(); //2传入该方法的参数的方法,可调用
d.invert(); //3该方法创建的对象的方法,可调用
b.kill(); //4该对象直接拥有的对象的方法,可调用
F f = b.getF();
f.rock(); //5 该对象依赖对象的实现的模块,不可调用。
}
}
3、最精炼的数据结构,是一个 只有公共变量、没有函数的类,叫数据传送对象(DTO).
- 错误处理
1、使用异常而非返回码。只要新增Exception的异常了而无需修改错误码枚举类
2、先写try-catch-finally语句。先构造try代码块的事物范围,维护好该范围的事务特征。
3、依调用者需要定义异常了将第三方API打包是好的实践手段。
4、定义常规流程。特例模式:创建一个雷或配置一个对象用来处理特例,客户就不用应付异常行为了
5、别返回NULL值