这本书的绝大部分思想都是非常合适其他语言的。只不过可能需要根据具体情况具体分析。
比如原则4要求“参数个数<=4”. 这一点非常适用Java,但是对于Python其实有更好的解决方案,一些很著名的开源类库也没有这样做。
但是比如编写短小、简单的代码单元,需要注意重用而不是复制黏贴代码,这些原则放到其他语言都是非常合适的。
可独立维护或者执行的最小代码集合。在Java之中代码单元就是方法或者构造函数。
原始代码:
public void start() {
if (inProgress) {
return;
}
inProgress = true;
// Update observers if player died:
if (!isAnyPlayerAlive()) {
for (LevelObserver o: observers) {
o.levelLost();
}
}
// Update observers if all pellets eaten:
if (remainingPellets() == 0) {
for (LevelObserver o: observers) {
o.levelWon();
}
}
}
重构1:把监控逻辑抽取出来
片段1
public void start() {
if (inProgress) {
return;
}
inProgress = true;
updateObservers(); // 被重构抽取出来的函数
}
片段2
private void updateObservers() {
// Update observers if player died:
if (!isAnyPlayerAlive()) {
for (LevelObserver o: observers) {
o.levelLost();
}
}
// Update observers if all pellets eaten:
if (remainingPellets() == 0) {
for (LevelObserver o: observers) {
o.levelWon();
}
}
}
进一步重构:进一步拆分 updateObservers
private void updateObservers() { // 被拆成了两个函数
updateObserversPlayerDied();
updateObserversPelletsEaten();
}
private void updateObserversPlayerDied() {
if (!isAnyPlayerAlive()) {
for (LevelObserver o: observers) {
o.levelLost();
}
}
}
private void updateObserversPelletsEaten() {
if (remainingPellets() == 0) {
for (LevelObserver o: observers) {
o.levelWon();
}
}
}
原始代码:
public Board createBoard(Square[][] grid) {
assert grid != null;
Board board = new Board(grid);
int width = board.getWidth();
int height = board.getHeight();
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
Square square = grid[x][y];
for (Direction dir: Direction.values()) {
int dirX = (width + x + dir.getDeltaX()) % width;
int dirY = (height + y + dir.getDeltaY()) % height;
Square neighbour = grid[dirX][dirY];
square.link(neighbour, dir);
}
}
}
return board;
}
:point_up_2:上面这个方法一共18行,已经超过15行的标准。首先可以尝试把 10~13 行单独抽取成一个函数,比如
public voide setLink(){
// skip
}
但是会发现,需要传入的参数太多,可以把整体重构成一个类 BoardCreator
class BoardCreator {
private Square[][] grid;
private Board board;
private int width;
private int height;
BoardCreator(Square[][] grid) {
// init ...
}
Board create() {
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
Square square = grid[x][y];
for (Direction dir: Direction.values()) {
setLink(square, dir, x, y);
}
}
}
return this.board;
}
private void setLink(Square square, Direction dir, int x, int y) {
// ...
}
}
:point_up_2: 上面这样重构的好处:
原始代码:
public List < Color > getFlagColors(Nationality nationality) {
List < Color > result;
switch (nationality) {
case DUTCH:
result = Arrays.asList(Color.RED, Color.WHITE, Color.BLUE);
break;
case GERMAN:
result = Arrays.asList(Color.BLACK, Color.RED, Color.YELLOW);
break;
case BELGIAN:
result = Arrays.asList(Color.BLACK, Color.YELLOW, Color.RED);
break;
case FRENCH:
result = Arrays.asList(Color.BLUE, Color.WHITE, Color.RED);
break;
case ITALIAN:
result = Arrays.asList(Color.GREEN, Color.WHITE, Color.RED);
break;
case UNCLASSIFIED:
default:
result = Arrays.asList(Color.GRAY);
break;
}
return result;
}
:point_up_2:上面代码之中, 使用 switch 做条件判断,本身逻辑还是很清晰的。但是如果还需要不断的添加新的国家的时候,如果忘记写 break 语句,返回的结果就错误了。这种小bug其实反而是我们日常主要的bug。
如何重构呢?重构的方法比较多,书上推荐的做法: 使用Map的数据结构 。
private static Map < Nationality, List < Color >> FLAGS =
new HashMap < Nationality, List < Color >> ();
static {
FLAGS.put(DUTCH, Arrays.asList(Color.RED, Color.WHITE, Color.BLUE));
FLAGS.put(GERMAN, Arrays.asList(Color.BLACK, Color.RED, Color.YELLOW));
FLAGS.put(BELGIAN, Arrays.asList(Color.BLACK, Color.YELLOW, Color.RED));
FLAGS.put(FRENCH, Arrays.asList(Color.BLUE, Color.WHITE, Color.RED));
FLAGS.put(ITALIAN, Arrays.asList(Color.GREEN, Color.WHITE, Color.RED));
}
public List < Color > getFlagColors(Nationality nationality) {
List < Color > colors = FLAGS.get(nationality);
return colors != null ? colors : Arrays.asList(Color.GRAY);
}
:point_up_2:如上, 这样分支点的数量就不是线性增加, 而是常量 2 了。
书中还提到了可以定义一个 Flag 接口,然后各个国家的国旗实现这个接口的方法。 比如:
public interface Flag {
List < Color > getColors();
}
public class DutchFlag implements Flag {
public List < Color > getColors() {
return Arrays.asList(Color.RED, Color.WHITE, Color.BLUE);
}
}
如果仅仅是针对这个例子, 我觉得这种方法不一定合适,因为这个数据结构很简单。 但是如果数据结构比较复杂, 我觉得这样才能体现出优势。
PS: 这个思考是个人的思考,并不是书上的内容。
if-else 分支。对其进行拆分也不能减小复杂度、或者减少分支点啊? 我想起之前同事展示的财务的相关代码。可能有20+个 if-else 分支。之前在做电商相关业务,计算物流费用的时候也遇到类似的场景。即使是在我们的API代码之中, 比如确定按照哪个维度进行Group就7~8个分支点。
是否有解决方案呢? 一个可能的方案拆分方法:拆分成两种。即:
比如类似 switch 这种分支点, 大部分情况下可能还是可以忍受的。因为毕竟分支点是线性增加,而且条件之间相互应该是独立的。 最难测试的是条件有先后顺序的情况 。
比如有一些比较复杂的SQL,不同的SQL可能只是统计的指标或者Group的字段不太一样。这种情况跟具体使用的语言无关。比较好的方法:
如果使用Python进行SQL模板拼装的话, 可以尝试一下 jinjasql 这个类库。
private voide render(int x, int y, int w, int h) {
// skip
}
上面的 x,y,w,h 是长方形的坐标与长宽。可以如下重构:
public class Rectangle{
// skip
}
private voide render(Rectangle) {
// skip
}
好处:在Java之中,如果多个参数一起传入,就需要很仔细的不要把参数位置弄混。特别是类型相同的情况之下。
对于Python就要好很多了,入参的时候可以指定关键字,这样即使参数位置调整了,在调用的时候也不需要很辛苦的修改调用代码。而且Python这种动态语言,编译的时候都无法检查出来。
比如我们项目之中一个拼接SQL的进行查询的函数。
def read_toutiao_performance_stat_report( start_day, end_day, filters_source=None, group_by_key=None, filters_bu=None, filters_app=None, filters_account=None, filters_campaign_id=None, filters_geo=None, filters_adset_id=None, by="daily", to_dollar=False, limit=None): pass
↑ 主要分成好几组:
调用方式:
dataset = fetch_report( start_day, end_day, filters_source=None, group_by_key=group_by_key, filters_bu=filters_bu, filters_app=filters_app, filters_account=filters_account, filters_campaign_id=filters_campaign_id, filters_adset_id=filters_adset_id, filters_geo=filters_geo, by=by, to_dollar=to_dollar, limit=limit)
这样就不会引起混乱了。
备注:这一条可能对Java比较适用,Python很多类库都有很长的参数列表,比如Pandas的 read_excel 函数:
def read_excel( io, sheet_name=0, header=0, names=None, index_col=None, usecols=None, squeeze=False, dtype=None, engine=None, converters=None, true_values=None, false_values=None, skiprows=None, nrows=None, na_values=None, keep_default_na=True, verbose=False, parse_dates=False, date_parser=None, thousands=None, comment=None, skipfooter=0, convert_float=True, mangle_dupe_cols=True, **kwds, ): pass
具体略
想说爱你不容易
原文标题:Automated Tests 。即仅仅是自动化测试。
GlobalProjectNamingStrategyConfiguration
这一条我感觉放在最后是最合适的,因为这一条才是最难最难的。
这个需要开发人员的经验积累,培养良好的习惯,跟自己的懒惰作斗争。
图片来源:https://zhuanlan.zhihu.com/p/81777441