代码整洁笔记——整洁类的书写准则

1.0整洁类的书写准则

1.1 合理地分布类中的代码

一般情况下,我们遵循变量列表在前,函数在后的原则。

类应该从一组变量列表开始。若有公有静态常量,应该最先出现,然后是私有静态变量,以及公有变量,私有变量。尽可能少的出现公有变量。

公共函数应该出现在变量列表之后。我们喜欢把由某个公共函数调用的私有工具函数紧跟在公共函数后面。

这样是符合自定向下的原则,让程序读起来像一篇报纸文章。

1.2 尽可能保持类的封装

我们喜欢保持变量和工具函数的私有性,但不执著于此。有时,我们需要用到protected变量或者工具,比如让测试可以访问到。然而,我们会尽可能使函数或变量保持私有,不对外暴露太多细节。放松封装,总是下策。

1.3 类应该短小

正如之前关于函数书写的论调。类的一条规则是短小,第二条规则还是要短小。

和函数一样,马上有个问题要出现,那就是,多小合适呢?
对于函数,我们通过计算代码行数来衡量大小,对于类,我们采用不同的衡量方法,那就是权责(responsibility)。

1.3.1 单一权责原则

单一权责(Single Responsibility Principle,SRP)认为,类或模块应有且只有一条加以修改的理由。

举个栗子,下面这个类足够短小了吗?

public class SuperDashboard extends JFrameimplements MetaDataUser
{
    public Component getLastFocusedComponent()
    public void setLastFocused(Component lastFocused)
    public int getMajorVersionNumber()
    public int getMinorVersionNumber()
    public int getBuildNumber()
}

答案是否定的,这个类不够“短小”。5个方法不算多,但是这个类虽方法少,但还是拥有太多权责。这个貌似很小的SuperDashboard类,却有两条关联度并不大的加以修改的理由:

第一, 它跟踪会随着软件每次发布而更新的版本信息(含有getMajorVersionNumber等方法)。
第二,它还在管理组件(含有getLastFocusedComponent方法)。

其实,鉴别权责(修改的理由)常常帮助我们在代码中认识到并创建出更好的抽象。

我们可以轻易地将SuperDashboard拆解成名为Version的类中,而这个名为Version的类,极可能在其他应用程序中得到复用:

public class Version
{
    public int getMajorVersionNumber()
    public int getMinorVersionNumber()
    public int getBuildNumber()
}

这样,这个类就大致做到了单一权责。

1.4 合理提高类的内聚性

我们希望类的内聚性保持在较高的水平。

何为类的内聚性?类的内聚性就是类中变量与方法之间的依赖关系。类中方法操作的变量越多,就越黏聚到类上,就代表类的内聚性高。

类应该只有少量的实体变量,类中的每个方法都应该操作一个或者多个这种变量。通常而言,如果一个类中的每个变量都被每个方法所使用,则该类具有最大的内聚性。一般来说,创建这种极大化的内聚类不可取,也不可能。

我们只希望内聚性保持在较高的水平。内聚性高,表示类中方法和变量相互依赖,相互结合成一个逻辑整体。

举个高内聚的例子:

public class Stack 
{
    private int topOfStack = 0;
    List<Integer> elements = new LinkedList<Integer>();
 
    public int size() 
    {
        return topOfStack;
    }
 
    public void push(int element)
    {
        topOfStack++;
        elements.add(element);
    }
 
    public int pop() throws PoppedWhenEmpty 
    {
        if (topOfStack == 0)
            throw new PoppedWhenEmpty();
        int element = elements.get(--topOfStack);
        elements.remove(topOfStack);
            return element;
    }
}

这个类非常内聚,在三个方法中,仅有size()方法没有使用所有的两个变量。

注意,保持函数和参数短小的策略,有时候会导致为一组子集方法所用的实体变量增加。我们应该尝试将这些方法拆分到两个或者多个类中,让新的类更为内聚。

1.5 有效地隔离修改

需求会改变,所以代码也会改变。在面向对象入门知识中我们学习到,具体类包含实现细节(代码),而抽象类则呈现概念。依赖于具体细节的客户类,当细节改变时,就会有风险。我们可以借助接口和抽象类来隔离这些细节带来的影响。

举个栗子,在一个设计场景下,我们以其设计直接依赖于TokyoStockExchange的Protfolio类,不如创建StockExchange接口,里面只声明一个方法:

public interface StockExchange
{
    MoneycurrentPrice(String symbol);
}

接着设计TokyoStockExchange类来实现这个接口:

public class TokyoStockExchange extends StockExchange
{
       //…
}

我们还要确保Portfolio的构造器接受作为参数StickExchange引用:

public Portfolio
{
    private StockExchange exchange;
    public Portfolio(StockExchange exchange)
    {
        this.exchange = exchange;
    }
    // ...
}

那么现在就可以为StockExchange接口创建可以测试的实现了,例如返回固定的股票现值。比如测试购买5股微软股票,我们下面的实现代码返回100美元的现值,然后再实现一个总投资价值为500美元的测试,那么大概代码则是:

public class PortfolioTest
{
    privateFixedStockExchangeStub exchange;
    privatePortfolio portfolio;
 
    @Before
    protected void setUp() throws Exception
    {
        exchange = new FixedStockExchangeStub();
        exchange.fix("MSFT", 100);
        portfolio = new Portfolio(exchange);
    }
 
    @Test
    public void GivenFiveMSFTTotalShouldBe500() throws Exception
    {
        portfolio.add(5, "MSFT");
        Assert.assertEquals(500,portfolio.value());
    }
}

如果系统解耦到足以这样测试的程度,也就更加灵活,更加可复用。部件之间的解耦代表着系统中的元素相互隔离得很好。隔离也让对系统每个元素的理解变得更加容易。

我们的Portfolio类不再是依赖于TokyoStockExchange类的实现细节,而是依赖于StockExchange接口这个抽象的概念,这样就隔离了特定的细节。而其实我们的类就遵循了另一条类的设计原则,依赖倒置原则(Dependency Inversion Principle , DIP),因为依赖倒置原则的本质,实际上就是认为类应该依赖于抽象,而不是依赖于具体细节。

2.0总结

  • 合理地分布类中的代码: 类中代码的分布顺序大致是:
    <1> 公有静态常量
    <2> 私有静态变量
    <3> 公有普通变量
    <4> 私有普通变量
    <5> 公共函数
    <6> 私有函数
  • 尽可能地保持类的封装: 尽可能使函数或变量保持私有,不对外暴露太多细节。
  • 类应该短小,尽量保持单一权责原则: 类或模块应有且只有一条加以修改的理由。
  • 合理提高类的内聚性: 我们希望类的内聚性保持在较高的水平。内聚性高,表示类中方法和变量相互依赖,相互结合成一个逻辑整体。
  • 有效地隔离修改: 类应该依赖于抽象,而不是依赖于具体细节。尽量对设计解耦,做好系统中的元素的相互隔离,做到更加灵活与可复用。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容