https://mp.weixin.qq.com/s/2jZGZA2Bmg8fuUrjlB9Gtg
https://blog.pragmaticengineer.com/a-comment-is-an-invitation-for-refactoring/
A Comment Is An Invitation For Refactoring
https://mp.weixin.qq.com/s/WoFJXcgljI44p5OhvKebPw
https://medium.com/@egonelbre/psychology-of-code-readability-d23b1ff1258a
https://zhuanlan.zhihu.com/p/46435063
提高代码稳壮性,肥朝认为最好的办法就是提前预防。实际项目中,我们在配置文件配置了各种参数。但是大家也知道,不同环境的配置参数,是会不一样的,难免会因为人为疏忽,导致某个环境的配置文件,少了一些关键参数,光靠肉眼来检查,必然是一个低效而又不可靠的方式。如果你不用该方式校验,很容易在某个特殊的场景下,才触发出坑。但是你采用这种方式,做了大量的启动时校验,一旦参数不合法,项目启动都启动不了,做到了防范于未然!
<dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.0.0.GA</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>4.2.0.Final</version> </dependency>
A Comment Is An Invitation For Refactoring
step2: 拜托你,再检查检查系统边界吧。
setp5: 拜托你,打点有用的日志吧。
setp6: 拜托你,试着把所有的能想到的监控全加上。
接口成功率啊,响应时间啊,错误告警啊,上下游告警啊,主机告警啊,数据库成功率告警啊,数据库响应时间告警啊。
setp7: 拜托你,针对告警想一下你的解决方案。
告警出来了,总得处理吧?提前想一想没什么坏处。
setp8: 拜托你,想一下怎么样让你的系统不会崩。
异地容灾?接口限流?接口防刷?服务降级?很多事情可以做,好好思考一下,好好思考一下。
https://medium.com/@egonelbre/psychology-of-code-readability-d23b1ff1258a
不集中的逻辑
我们偏爱连续的,平铺直叙的而且独立的逻辑。让我们来解释一下这些是啥意思
- 连续的:第2行应该要和第1行有关系,它们被摆得那么近,肯定是要表达什么因果关系
- 平铺直叙的:你从头往下阅读代码,机器也是从头往下执行这段代码
- 独立的:你所有需要关心的东西都在这里
有三个原因会使得逻辑分散:
- 编码风格:全局变量,SIMD intrinsics v.s. SPMD 风格 GPU 计算,回调 v.s. 协程
- 代码重用:为了重用代码,我们需要把多个执行路径合并成通用的一个
- 非功能性需求:它和功能性代码在空间上(源代码)和时间上(运行时)都是在一起的
起码,分个层(建立多层次的抽象)
用facade模式控制外部访问
考虑拓展性(问问:如果xx怎么办?)
不再写面条式的代码
使用ddd构建聚合的概念
使用版本号 保证聚合的一致性
参考
- 《企业应用架构模式》
- 《领域驱动设计》
https://github.com/thoughtbot/guides/tree/master/best-practices
These are not to be blindly followed; strive to understand these and ask when in doubt.
Don't duplicate the functionality of a built-in library.
Don't swallow exceptions or "fail silently."
Don't write code that guesses at future functionality.
Exceptions should be exceptional.
Keep the code simple.
Object-Oriented Design
Avoid global variables.
Avoid long parameter lists.
Limit collaborators of an object (entities an object depends on).
Limit an object's dependencies (entities that depend on an object).
Prefer composition over inheritance.
Prefer small methods. Between one and five lines is best.
Prefer small classes with a single, well-defined responsibility. When a class exceeds 100 lines, it may be doing too many things.
Tell, don't ask.
Postgres
Avoid multicolumn indexes in Postgres. It combines multiple indexes efficiently. Optimize later with a compound index if needed.
Consider a partial index for queries on booleans.
Constrain most columns as NOT NULL.
Index foreign keys.
Use an ORDER BY clause on queries where the results will be displayed to a user, as queries without one may return results in a changing, arbitrary order.
Email
Use SendGrid or Amazon SES to deliver email in staging and production environments.
Use a tool like ActionMailer Preview to look at each created or updated mailer view before merging. Use MailView gem unless using Rails version 4.1.0 or later.
http://luopq.com/2016/02/21/write-good-function
https://dzone.com/articles/dont-add-unnecessary-checks-to-your-code-pretty-pl
http://www.cnblogs.com/lazio10000/p/5413439.html
http://blog.csdn.net/wangshiqilin_fjy/article/details/50824476
2、不要有重复逻辑的代码
3、函数的行数不要超过20行,这里的20行只是个大概,并不一定是这个数字
4、减少嵌套
Object-Oriented Design
Avoid global variables.
Avoid long parameter lists.
Limit collaborators of an object (entities an object depends on).
Limit an object's dependencies (entities that depend on an object).
Prefer composition over inheritance.
Prefer small methods. Between one and five lines is best.
Prefer small classes with a single, well-defined responsibility. When a class exceeds 100 lines, it may be doing too many things.
Tell, don't ask.
Postgres
Avoid multicolumn indexes in Postgres. It combines multiple indexes efficiently. Optimize later with a compound index if needed.
Consider a partial index for queries on booleans.
Constrain most columns as NOT NULL.
Index foreign keys.
Use an ORDER BY clause on queries where the results will be displayed to a user, as queries without one may return results in a changing, arbitrary order.
Use SendGrid or Amazon SES to deliver email in staging and production environments.
Use a tool like ActionMailer Preview to look at each created or updated mailer view before merging. Use MailView gem unless using Rails version 4.1.0 or later.
http://luopq.com/2016/02/21/write-good-function
https://dzone.com/articles/dont-add-unnecessary-checks-to-your-code-pretty-pl
Defensive programming suggests that we should add various checks to our code to ensure the presence and proper shape and type of data. But there is one important rule – only add a check if you know that thing can really happen. Don’t add random checks just to be sure – because you are misleading the next developer.
Imagine you need to call
.indexOf
on product_code
. You might be tempted to add
Now you are safe and your code can’t fail. But when I read this code, I immediately think:
Oh, I believed product_code to be always a string but obviously it is not always the case. It must have happened in the past or the author wouldn’t have added this check. I wonder what are the possible non-string values. And what does it mean and how should I handle them?
So by adding this check you have eroded significantly my trust in the data or rather trust in my mental model of the data (domains, possible values) and it has become much more fuzzy. Now I have to always assume that product_code can be something (what?!) else than a string, check for it everywhere and wonder what to do about it.
Say you’re reading a program and you see some lines like this:
new ArrayBlockingQueue(10, false); box.pack_start(child, false, true);
You don’t know what the booleans mean. Say you’re reading it and you see this:
new ArrayBlockingQueue(10, Policy.FAIR); box.pack_start(child, Packing.FILL);
Which is better?
There’s only one time that a boolean is OK, and that’s when the name of the method (or keyword, in a language that has keyword args) already describes it:
queue.setFair(false); box.set_expand(child, false);
Otherwise, no booleans. I know you’re too lazy to create an enum or flags type, but do it anyway.
Numeric types, for APIs where the number is expected to be a literal rather than a variable name, often have the same problem. But it’s a bit harder to solve in that case. The best solution may be for API users to use named constants.
Mystery-booleans are easy to avoid, though, so just do it.
The problem is reading code that calls this method, for example:
File[] withText = finder.find(root, "java", true);
What is the meaning of "true" in the context of this call? In order to understand what the code is doing, we need to go back to the method signature (assuming we have the code for it,) or check the documentation (if any.)
IMHO, it is better to split that method in two:
public File[] find(File root, String text) public File[] findRecursively(File root, String text)
Or something like that. We can still argue that the method
find
is not communicating its intention. It would be difficult to know if the search is recursive or not when reading code using it. Probably we can change it to findNotRecursively
or findInFolder
.
There is one place where
boolean
parameters are not too bad: JavaBeans setters. The reason is simple, they take only one argument, and the name of the setter pretty much documents the purpose of the parameter. For example:window.setVisible(true);
On the other hand, the worst use of
boolean
parameters I've ever seen is constructors. Even if a constructor takes only one parameter (a boolean
parameter,) it is very difficult to understand its purpose. For example:/** * Creates a new TestHierarchy. * @param ignoreExisting indicates whether extant components should be omitted from the hierarchy. */ public TestHierarchy(boolean ignoreExisting)
Now let's call that constructor:
Hierarchy hierarchy = new TestHierarchy(true);
Once again, it is difficult to tell what "true" means, even if we are familiar with this code. Wouldn't it be better to use a static factory method? Something like:
// using static imports Hierarchy hierarchy = hierarchyWithoutExisting();
Actually what is really bad is the use of "three-state" booleans. That is, using a
Boolean
as parameter where the "valid" states are: Boolean.TRUE
, Boolean.FALSE
and surprisingly, null
! I used to think that "three-state" booleans were a plague present only in the Microsoft world, but OMG! I have found a lot of Java code using them! An alternative to "three-state" booleans? Java enums!
In summary, let's not use boolean parameters. They don't communicate their purpose. Instead, we can:
- in the case of methods, split them in two
- in the case of constructors, use static factory methods
- in the case of "three-state" booleans, use Java enums
However, the use of a boolean argument reduces readability. For example, in a method such as
workspace.createFile(name, true)
, what does “false” mean? It is much more intention-revealing to call a method such asworkspace.createTempFile(name)
. Furthermore, boolean arguments always create duplication, either of code or of responsibility, and often of a very insidious nature. This is because the called method contains a runtime test to decide between two different code paths, despite thet fact that the calling method already knows which branch it requires! The duplication not only wastes a little runtime, it also spreads knowledge and responsibility further than is necessary. The resulting code is more closely coupled than the design it represents. These effects are magnified exponentially each time the boolean argument is passed on in a further method call.
Therefore avoid the need for a boolean argument by creating two versions of the method, in which the meaning of the boolean is reflected in the names of the new methods. For example, replace
File createFile(String name, boolean istemp)
by
File createFile(String name) File createTempFile(String name)
If the resulting new methods have similar logic, extract the duplicate code into shared methods that can be called by each.
(Here’s a fun story on a related theme.)
在我看来没有充分的理由不应该返回null,因为方法的使用者并不知道在何种条件下会得到null,从而导致臭名昭著的NullReferenceException异常。
如果由于其他原因导致无法得到一个User,我们应该抛出一个明确的异常供上层服务决定如何处理:
public User GetUser(string userName, string password) { if ( /*for some reason*/) return new SpecificException("can't get this user because ...."); return DB.GetByUserId(userId); }
在我读过的开源项目中我几乎没有见到过return null的写法。能让我一时想到的两个linq方法FirstOrDefault()和LastOrDefault(),这两个方法通过命名的方式提醒使用者该方法会返回null。
这不是最主要的问题,最重要的问题是你在开发过程中隐藏了bug,如果当时你没加这个Try...Catch,恐怕你早就发现这个bug了,因为程序压根就跑不下去。
异常信息应该由最上层的框架捕获,比如MVC中有ExceptionFilter,你可以在这里记录详细日志,别把黄页抛给用户就可以了。我想写一个Try...Catch的场景,但是居然一下子想不出来一个很好的场景,因为真真需要写Try...Catch的场景是很少的,你一旦想写Try...Catch,首先想想你是不是在故意隐瞒Bug.
前阵子在修改别人的代码的时候,发现这样一段逻辑
只要你干过两三年的编程,就有可能曾被别人的糟糕代码绊倒过。如果你编程不止两三年,也有可能被这种代码拖过后腿。有些团队在项目初期进展很顺利,但是过一段时间久变得慢如蜗牛。对代码的每次修改都会影响到其他的模块。修改无小事,每次添加或者修改代码,都必须理清思路,想想会对哪些模块产生影响,做好单元测试。
我喜欢优雅、高效的代码:
● 逻辑应该是清晰的,bug难以隐藏;
●依赖最少,易于维护;
●错误处理完全根据一个明确的策略;
●性能接近最佳化,避免代码混乱和无原则的优化;
●整洁的代码只做一件事。
整洁代码的重要性
1:整洁的代码能够促进团队合作;
2:整洁的代码能够减少bug的产生;
3:整洁的代码可以降低维护成本;
4:整洁的代码有助于进行代码审查;
5:整洁的代码有助于提升我们的能力;
2:整洁的代码能够减少bug的产生;
3:整洁的代码可以降低维护成本;
4:整洁的代码有助于进行代码审查;
5:整洁的代码有助于提升我们的能力;
在我们真实的产品中有这么一个类:
public class Class1
{
public decimal Calculate(decimal amount, int type, int years)
{
decimal result = 0;
decimal disc = (years > 5) ? (decimal)5/100 : (decimal)years/100;
if (type == 1)
{
result = amount;
}
else if (type == 2)
{
result = (amount - (0.m * amount)) - disc * (amount - (0.m * amount));
}
else if (type == 3)
{
result = (0.m * amount) - disc * (0.m * amount);
}
else if (type == 4)
{
result = (amount - (0.m * amount)) - disc * (amount - (0.m * amount));
}
return result;
}
}
这是一段非常糟糕的代码(二胡:如果你没觉得这段代码很糟糕,那你目前状态可能很糟糕了),我们不太清楚这个类的目的是什么。它可能是一个网上商城的折扣管理类,负责为客户计算折扣。
这个类完全具备了不可读、不可维护、不可扩展的特点,它使用了很多坏的实践和反模式的设计。
下面我们逐步分析这里到底有多少问题?
- 命名问题 - 我们只能通过猜测这个类到底是为了计算什么。这实在是在浪费时间。
如果我们没有相关文档或者重构这段代码,那我们下一次恐怕需要花大量的时间才能知道这段代码的具体含义。 - 魔数 - 在这个例子中,你能猜到变量type是指客户账户的状态吗。通过if-else来选择计算优惠后的产品价格。
现在,我们压根不知道账户状态1,2,3,4分别是什么意思。
此外,你知道0.1,0.7,0.5都是什么意思吗?
让我们想象一下,如果你要修改下面这行代码:
result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount));
- 隐藏的BUG - 因为代码非常糟糕,我们可能会错过非常重要的事情。试想一下,如果我们的系统中新增了一类账户状态,而新的账户等级不满足任何一个if-else条件。这时,返回值会固定为0。
- 不可读 - 我们不得不承认,这是一段不可读的代码。不可读=更多的理解时间+增加产生错误的风险
- DRY – 不要产生重复的代码
我们可能不能一眼就找出它们,但它们确实存在。
例如:disc *(amount - (0.1m * amount));
同样的逻辑:
disc *(amount - (0.5m * amount));
这里只有一个数值不一样。如果我们无法摆脱重复的代码,我们会遇到很多问题。比如某段代码在五个地方有重复,当我们需要修改这部分逻辑时,你很可能只修改到了2至3处。 - 单一职责原则
这个类至少具有三个职责:
1 选择计算算法
2 根据账户状态计算折扣
3 根据账户网龄计算折扣
它违反了单一职责原则。这会带来什么风险?如果我们将要修改第三个功能的话,会影响到另外第二个功能。这就意味着,我们每次修改都会改动我们本不想修改的代码。因此,我们不得不对整个类进行测试,这实在很浪费时间。
这是良好代码的最重要方面之一。我们只需要改变方法,参数和变量的命名。现在,我们可以确切的知道下面的类是负责什么的了。
在C#中避免魔数我们一般采用枚举来替换它们。这里使用AccountStatus 枚举来替换if-else中的魔数。
public enum AccountStatus { NotRegistered = 1, SimpleCustomer = 2, ValuableCustomer = 3, MostValuableCustomer = 4 }
现在我们来看看重构后的类,我们可以很容易的说出哪一个账户状态应该用什么算法来计算折扣。混合账户状态的风险迅速的下降了。
更多的代码可读性
在这一步中,我们使用switch-case 来替换 if-else if来增强代码可读性。
同时,我还将某些长度很长的语句才分成两行。比如:**priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage * (price - (0.5m * price)));**
被修改为:
**priceAfterDiscount = (price - (0.5m * price));priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);**
消除隐藏的BUG
正如我们之前提到的,我们的ApplyDiscount方法可能将为新的客户状态返回0。
我们怎样才能解决这个问题?答案就是抛出NotImplementedException。
当我们的方法获取账户状态作为输入参数,但是参数值可能包含我们未设计到的未知情况。这时,我们不能什么也不做,抛出异常是这时候最好的做法。
我们怎样才能解决这个问题?答案就是抛出NotImplementedException。
当我们的方法获取账户状态作为输入参数,但是参数值可能包含我们未设计到的未知情况。这时,我们不能什么也不做,抛出异常是这时候最好的做法。
http://blog.csdn.net/wangshiqilin_fjy/article/details/50824476
比如说,你代码中有主动去请求某项资源的操作,那一定要判断一下请求是否成功,比如我早上发现的“杀气”。还有,不要随便使用return,
特别是一个void函数,我就看过一个void函数,里面不止一个return啊,我连哪里返回了都不知道!!!最后,程序中尽量多一些log,多一些捕捉异常的语句,if——else一定要配对使用(哪怕你的else里面是空,也要有一个),诸如此类。
尽可能完整地描述函数所做的所有事情
有的开发者可能觉得相较于长函数名来说,短函数名看起来可能更简洁,看起来也更舒服。但是通常来说,函数名称越短其描述的意思越抽象。函数使用者对函数的第一印象就是函数名称,进而了解函数的功能,我们应该尽可能地描述到函数所做的所有事情,防止使用者不知道或误解造成潜在的错误。
举个例子,假设我们做一个添加评论的功能,添加完毕后并返回评论总数量,如何命名比较合适呢?
举个例子,假设我们做一个添加评论的功能,添加完毕后并返回评论总数量,如何命名比较合适呢?
1 2 3 4 5 | // 描述不够完整的函数名 var count = function addComment() {}; // 描述完整的函数名 var count = function addCommentAndReturnCount() {}; |
这只是简单的一个例子,实际开发中可能会遇到得更多复杂的情况,单一职责原则是我们开发函数要遵守的准则,但是有时候无法做到函数单一职责时,请记得函数名应该尽可能地描述所有事情。当你无法命名一个函数时,应该分析一下,这个函数的编写是否科学,有什么办法可以去优化它。
采用准确的描述动词
这一点对母语非英语的开发者来说应该是比较难的一点,想要提高这方面的能力,最主要的还是要提高词汇量,多阅读优秀代码积累经验。
这里简单说说我自己的一些感想和看法:
1、不要采用太抽象广泛的单词
很多开发人员会采用一个比较宽泛的动词来为函数命名,最典型的一个例子就是get这个单词。我们平时开发中经常会通过各种不同的方式拿到数据,但是每一种方式都用get就有点太抽象了。具体如何命名,要具体分析:
(1)简单的返回数据
这里简单说说我自己的一些感想和看法:
1、不要采用太抽象广泛的单词
很多开发人员会采用一个比较宽泛的动词来为函数命名,最典型的一个例子就是get这个单词。我们平时开发中经常会通过各种不同的方式拿到数据,但是每一种方式都用get就有点太抽象了。具体如何命名,要具体分析:
(1)简单的返回数据
1 2 3 | Person.prototype.getFullName = function() { return this.firstName = this.lastName; } |
(2)从远程获取数据
1 2 3 4 5 | var fetchPersons = function () { ... $.ajax({ }) } |
(3)从本地存储加载数据
1
| var loadPersons = function () {};
|
(4)通过计算获取数据
1
| var calculateTotal = function () {};
|
(5)从数组中查找数据
1
| var findSth = function (arr) {};
|
(6)从一些数据生成或得到
1 2 3 | var createSth = function (data) {}; var buildSth = function (data) {}; var parseSth = function(data) {}; |
这是一个简单的例子,我们平时开发中遇到的情况肯定会复杂得多,关键还是靠单词的积累,多阅读优秀源码
下面是整理的一些常用的对仗词,大家可以参考使用
1 2 3 4 5 6 | add/remove increment/decrement open/close begin/end insert/delete show/hide create/destory lock/unlock source/target first/last min/max star/stop get/put next/previous up/down get/set old/new |
参数数量
毫无疑问,函数参数越多,函数的易用性就越差,因为使用者需要严格眼中参数列表依次输入参数,如果某个参数输错,将导致不可意料的结果。
但是,函数参数就一定越少越好吗?我们来看看下面的例子:
但是,函数参数就一定越少越好吗?我们来看看下面的例子:
1 2 3 4 5 6 7 | var count = 0; var unitPrice = 1.5; .... ... var calculatePrice = function () { return count * unitPrice; } |
在这个例子中,我们通过calculatePrice这个函数来计算价格,函数不接收任何参数,直接通过两个全局变量unitPrice和count进行计算。这种函数的定义对使用者来说非常方便,直接调用即可,不用输入任何参数。但是这里可能会有潜在的bug:全局变量可能在其他地方被修改成其他值了,难以进行单元测试等等问题。所以,这个函数可以传入数量和价格信息:
1 2 3 | var calculatePrice = function(count, unitPrice) { return count * unitPrice; } |
这种方式下,函数使用者在使用时,要传入参数进行调用,避免了全局变量可能存在的问题。另外也降低了耦合,提高了可测试性,在测试的时候就不必依赖于全局变量。
当然,在保证函数不依赖于全局变量和测试性的情况下,函数参数还是越少越好。《代码大全》中提出将函数的参数限制在7个以内,这个可以作为我们的参考。
有的时候,我们不可避免地要使用超过10个以上函数,在这中情况下,我们可以考虑将类似的参数构造成一个类,我们来看看一个典型的例子。
我相信大家平时一定做过这样的功能,列表筛选,其中涉及到各种条件的筛选,排序,分页等等功能,如果将参数一个一个地列出来必定会很长,例如:
有的时候,我们不可避免地要使用超过10个以上函数,在这中情况下,我们可以考虑将类似的参数构造成一个类,我们来看看一个典型的例子。
我相信大家平时一定做过这样的功能,列表筛选,其中涉及到各种条件的筛选,排序,分页等等功能,如果将参数一个一个地列出来必定会很长,例如:
1
| var filterHotel = function (city, checkIn, checkOut, price, star, position, wifi, meal, sort, pageIndex) {}
|
这是一个筛选酒店的函数,其中的参数分别是城市,入住和退房时间,价格,星级,位置,是否有wifi,是否有早餐,排序,页码等等,实际的情况可能会更多。在这种参数特别多的情况下,我们可以考虑将一些相似的参数提取成类出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function DatePlace (city, checkIn, checkOut){ this.city = city; this.checkIn = checkIn; this.checkOut = checkOut } function HotelFeature (price, star, position, wifi, meal){ this.price = price; this.star = star; this.position = position; this.wifi = wifi; this.meal = meal; } var filterHotel = function (datePlce, hotelFeature, sort, pageIndex) {}; |
将多个参数提取成对象了,虽然对象数量增多了,但是函数参数更清晰了,调用起来也更方便了。
尽量不要使用bool类型作为参数
有的时候,我们会写出使用bool作为参数的情况,比如:
1 2 3 4 5 6 7 8 9 | var getProduct = function(finished) { if(finished){ } else{ } } // 调用 getProduct(true); |
如果没有注释,使用者看到这样的代码:getProduct(true),他肯定搞不清楚true是代表什么意思,还要去查看函数定义才能明白这个函数是如何使用的。这就意味着这个函数不够清晰,就应该考虑去优化它。通常有两种方式去优化它:
(1)将函数一分为二,分成两个函数getFinishedProduct和getUnFinishedProduct
(2)将bool转换成有意义的枚举getProduct(ProductStatus)
(1)将函数一分为二,分成两个函数getFinishedProduct和getUnFinishedProduct
(2)将bool转换成有意义的枚举getProduct(ProductStatus)
不要修改输入参数
如果输入参数在函数内被修改了,很有可能造成潜在的bug,而且使用者不知道调用函数后居然会修改函数参数。
正确使用输入参数的做法应该是只传入参数用于函数调用。
如果不可避免地要修改,一定要在注释中说明。
正确使用输入参数的做法应该是只传入参数用于函数调用。
如果不可避免地要修改,一定要在注释中说明。
尽量不要使用输出参数
使用输出参数说明这个函数不只做了一件事情,而且使用者使用的时候可能还会感到困惑。正确的方式应该是分解函数,让函数只做一件事。
相关操作放在一起
有的时候,我们会在一个函数内进行一系列的操作来完成一个功能,比如:
1 2 3 4 5 6 7 8 9 | var calculateTotalPrice = function() { var roomCount = getRoomCount(); var mealCount = getMealCount(); var roomPrice = getRoomPrice(roomCount); var mealPrice = getMealPrice(mealCount); return roomPrice + mealPrice; } |
这段代码计算了房间价格和早餐价格,然后将两者相加返回总价格。
这段代码乍一看,没有什么问题,但是我们分析代码,我们先是分别获取了房间数量和早餐数量,然后再通过房间数量和早餐数量分别计算两者的价格。这种情况下,房间数量和计算房间价格的代码分散在了两个位置,早餐价格的计算也是分散到了两个位置。也就是两部分相关的代码分散在了各处,这样阅读起代码来逻辑会略显不通,代码组织不够好。我们应该让相关的语句和操作放在一起,也有利于重构代码。我们修改如下:
这段代码乍一看,没有什么问题,但是我们分析代码,我们先是分别获取了房间数量和早餐数量,然后再通过房间数量和早餐数量分别计算两者的价格。这种情况下,房间数量和计算房间价格的代码分散在了两个位置,早餐价格的计算也是分散到了两个位置。也就是两部分相关的代码分散在了各处,这样阅读起代码来逻辑会略显不通,代码组织不够好。我们应该让相关的语句和操作放在一起,也有利于重构代码。我们修改如下:
1 2 3 4 5 6 7 8 9 | var calculateTotalPrice = function() { var roomCount = getRoomCount(); var roomPrice = getRoomPrice(roomCount); var mealCount = getMealCount(); var mealPrice = getMealPrice(mealCount); return roomPrice + mealPrice; } |
我们将相关的操作放在一起,这样代码看起来更清晰了,而且也更容易重构了。
if语句嵌套的问题
多层if语句嵌套是常有的事情,有什么好的方法可以减少嵌套呢?
1、尽早终止函数或返回数据
如果符合某个条件下可以直接终止函数,则应该将这个条件放在第一位。我们来看看下面的例子。
1、尽早终止函数或返回数据
如果符合某个条件下可以直接终止函数,则应该将这个条件放在第一位。我们来看看下面的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | if(condition1) { if(condition2){ if(condition3){ } else{ return; } } else{ return; } } else { return; } |
这段代码中if语句嵌套了3层,看起来已经很复杂了,我们可以将最后面的return提取到最前面去。
1 2 3 4 5 6 7 8 9 10 | if(!condition1){ return; } if(!condition2){ return; } if(!condition3){ return; } //doSth |
这段代码中,我们把condition1等于false的语句提取到前面,直接终止函数,将多层嵌套的if语句重构成只有一层if语句,代码也更清晰了。
注意:一般情况下,我们写if语句会将条件为true的情况写在前面,这也比较符合我们的思维习惯。如果是多层嵌套的情况,应该优先减少if语句的嵌套
2、不适用if语句或switch语句
条件语句一般来说是不可避免的,有的时候,我们要判断很多条件就会写很多if-elseif语句,嵌套的话,就更加麻烦了。如果有一天增加了新需求,我们就要去增加一个if分支语句,这样不仅修改起来麻烦,而且容易出错。《代码大全》提出的表驱动法可以有效地解决if语句带来的问题。我们来看下面这个例子:
条件语句一般来说是不可避免的,有的时候,我们要判断很多条件就会写很多if-elseif语句,嵌套的话,就更加麻烦了。如果有一天增加了新需求,我们就要去增加一个if分支语句,这样不仅修改起来麻烦,而且容易出错。《代码大全》提出的表驱动法可以有效地解决if语句带来的问题。我们来看下面这个例子:
1 2 3 4 5 6 7 8 9 10 11 12 | if(condition == “case1”){ return 1; } elseif(condition == “case2”){ return 2; } elseif(condition == “case3”){ return 3; } elseif(condition == “case4”){ return 4; } |
这段代码分别依次判断了四种情况,如果再增加一种情况,我们就要再新增一个if分支,这样就可能造成潜在的问题,如何去优化这段代码呢?我们可以采用一个Map或Dictionary来将每一种情况和相应值一一对应。
1 2 3 4 5 6 7 | var map = { "case1":1, "case2":2, "case3":3, "case4":4 } return map[condition]; |
通过map优化后,整个代码不仅更加简洁,修改起来也更方便而且不易出错了。
当然,很多时候我们的条件判断语句并不是这么简单的,可能会涉及到复杂的逻辑运算,大家可以查看《代码大全》第18章,其中有详细的介绍。
当然,很多时候我们的条件判断语句并不是这么简单的,可能会涉及到复杂的逻辑运算,大家可以查看《代码大全》第18章,其中有详细的介绍。
3、提取内层嵌套为一个函数进行调用
多层嵌套的时候,我们还可以将内层嵌套提取到一个新的函数中,然后调用该函数,这样代码也就更清晰了。
多层嵌套的时候,我们还可以将内层嵌套提取到一个新的函数中,然后调用该函数,这样代码也就更清晰了。
for循环嵌套优化
for循环嵌套相比于if嵌套来说更加复杂,阅读起来会更麻烦,下面说说几点要注意的东西:
1、最多只能两层for循环嵌套
2、提取内层循环到新函数中
3、多层循环时,不要简单地位索引变量命名为i,j,k等,容易造成混淆,要有具体的意思
1、最多只能两层for循环嵌套
2、提取内层循环到新函数中
3、多层循环时,不要简单地位索引变量命名为i,j,k等,容易造成混淆,要有具体的意思
提取复杂逻辑,语义化
有的时候,我们会写出一些比较复杂的逻辑,阅读代码的人看到后可能搞不清楚要做什么,这个时候,就应该提取出这段复杂的逻辑代码。
1 2 3 | if (age > 18 && gender == "man") { //doSth } |
这段代码表示当年龄大于18并且是男性的话,可以doSth,但是还是不够清晰,可以将其提取出来
1 2 3 4 5 6 7 8 9 | var canDoSth = function (age, gender){ return age > 18 && gender == "man"; } ... ... ... if(canDoSth(age, gender)){ //doSth } |
虽说多了一个函数,但是代码更加清晰和语义化了。
1、准确地对变量、函数命名2、不要有重复逻辑的代码
3、函数的行数不要超过20行,这里的20行只是个大概,并不一定是这个数字
4、减少嵌套