06 | Ruby on Rails:如何分析一个软件的接口?

你好!我是郑晔。

在上一讲中,我以Spring的DI容器为例,给你讲解了如何理解一个项目的模型。在模型之后,下一步就该是接口了。

在任何一个项目中,接口的数量都不是一个小数目。仅仅一个普通的程序库,里面的接口少则几十个,多则成百上千。难道我们理解接口,就是要一个一个地读这些接口吗?

显然,你不太可能把所有的接口细节都记住。我写Java程序差不多 20 年了,但很多JDK里的类我都不了解。甚至有时候,还没有等我去了解这个类,它就过时了。

那么,如何才能从纷繁复杂的接口中,披荆斩棘而出呢?我给你个方法:找主线,看风格

找主线的意思是,你需要找到一条功能主线,建立起对这个项目结构性的认知,而不是一上来就把精力放在每一个接口的细节上。你对细节部分的了解会随着你对项目的深入而逐渐增加。而有了主线后,你就有了着力点,就可以不断深入了。

但是,我们要学习的不只是这些接口的用法,要想从项目的接口设计上学到更多,这就需要你关注它所引导的风格,换句话说,就是它希望你怎样使用它,或是怎样在上面继续开发。

从一个项目的接口风格中,我们不难看出设计者的品位。我们常把编程视为一种艺术,而在接口的设计上就能窥见一二。这些内容是我们在学习软件设计时,应该慢慢品味的。

为什么要看风格,还有一个很重要的原因,就是你要维护项目的一致性,必须有一个统一的风格。有不少项目,里面会共存多种不同风格的接口,就是每个人都在设计自己习惯的接口,那势必会造成混乱。

这一讲,我们就来一起来学习怎样看接口,我选择的项目是Ruby on Rails,因为它的接口设计风格是带给我最多震撼的,无论是编程接口的优雅,还是开发过程接口的顺畅。不过,正如我在第4讲所说,看设计要先看模型。所以,我们还是先快速地了解一下Ruby on Rails的模型。

Ruby on Rails模型

如果你是一个比较年轻的程序员,Ruby on Rails这个名字可能会让你有一些陌生。但是在十多年前,它初出茅庐之际,可是给行业带来了极大的冲击。只是后来时运不济,编程模型发生了大的改变,使它失去了行业领导者的地位。这个故事还是要从你最熟悉的Web开发说起。

自从互联网兴起,人们对于Web开发的探索就从未停止过。

最早期的Web开发只是静态页面的开发,那时候,你只要熟悉HTML,就可以说自己懂Web开发了。后来,人们不再满足于静态页面,开始尝试编写有动态效果的页面。

一方面,浏览器开始支持JavaScript,让页面本身有了动态效果;另一方面,有人开始制作后台服务,在页面之间切换的时候,也可以有动态的效果。那个时候出现了像CGI(Common Gateway Interface)这样的编程规范。

当Java世界里出现了Servlet、JSP这些规范,Web开发也逐渐由小打小闹变成了企业开发的主力,越来越多的公司开始正视Web开发。因为这些规范很沉重,一些号称要简化Web开发的框架开始出现,比如:Struts、Webwork以及Spring MVC等等。

这些框架的出现,让Web开发摆脱了Servlet的初级阶段,使MVC模式成为了Web开发的主流。但即便如此,那个时候的Java Web开发依然是沉重的,比如写一个Web应用,光是配置文件就足以把人逼疯。

Ruby on Rails正是在这样的背景下横空出世的。为了叙述方便,后面我就把Ruby on Rails简称Rails了。

从模型上讲,Rails是标准的基于MVC模型进行开发的Web框架。在这一点上,它没有什么特殊的,它给行业带来巨大冲击的是它的接口设计。

Rails一个重要的设计理念就是约定优于配置,无需配置,按照缺省的风格就可以完成基本的功能,这样的理念贯穿在Rails各个接口的设计中。

接下来,我们就来看Rails的接口。

前面我提到过理解接口应该先找主线,找到项目主线的一个方法就是从起步走文档开始,因为它会把项目最基本的用法展现给你,你可以轻松地找到主线

Rails的起步走文档做得就非常好,主线可以说是一目了然。它用了一个Web项目帮你介绍了Rails开发的基本过程,通过这个过程,你就对Rails有了初步的印象。

有了主线之后,我们就要开始从中了解接口的风格。Rails给我们提供的三种接口,分别是:

  • Web应用对外暴露的接口:REST API;
  • 程序员写程序时用到的接口:API;
  • 程序员在开发过程中用到的接口:命令行。

接下来,我们就一个个地深入其中,了解它们的风格,以及它们给行业带来的不同思考。

REST 接口

先说应用对外暴露的接口:REST API。REST如今已经成为很多人耳熟能详的名词,它把Web 的各种信息当作资源。既然是资源,它就可以对这些Web信息做各种操作,这些操作对应着HTTP的各种动词(GET、POST、PUT、DELETE等)。

REST当年的问世是Roy Fielding博士为了纠正大家对HTTP的误用。 REST刚出来的时候,开发者普遍觉得这是一个好的想法,但怎么落地呢?没有几个人想得清楚。

Rails恰逢其时地出现了。Rails对REST的使用方式做了一个约定。只要你遵循Rails的惯用写法,写出来的结果基本上就是符合REST结构的,也就是说,Rails把REST这个模型用一种更实用的方式落地了。

Rails.application.routes.draw do
  ...
  resources :articles
  ...
end

在用Rails写程序的时候,你只要添加一个resource进去,它就会替你规划好这个资源应该如何去写、怎么设计URL、用哪些HTTP动词,以及它们对应到哪些方法。

$ bin/rails routes
      Prefix Verb   URI Pattern                  Controller#Action
    articles GET    /articles(.:format)          articles#index
             POST   /articles(.:format)          articles#create
 new_article GET    /articles/new(.:format)      articles#new
edit_article GET    /articles/:id/edit(.:format) articles#edit
     article GET    /articles/:id(.:format)      articles#show
             PATCH  /articles/:id(.:format)      articles#update
             PUT    /articles/:id(.:format)      articles#update
             DELETE /articles/:id(.:format)      articles#destroy
        root GET    /                            welcome#index

看了Rails给你的这个映射关系后,你就知道自己该怎么写代码了。这就是一种约定,不需要你费心思考,因为这是人家总结出来的行业中的最佳实践。只要按照这个规范写,你写的就是一个符合REST规范的代码,这就是Rails引导的外部接口风格。

API 接口

我们再来看API接口。当年我接触Rails时,最让我感到震惊的是它的数据库查询方式,与传统开发的风格截然不同,就这么简单的一句:

Article.find_by_title("foo")

要知道,那个时候用Java写程序,即便是想做一个最简单的查询,写的代码也是相当多的。我们不仅要创建一个对象,还要写对应的SQL语句,还要把查询出来的结果,按照一定的规则组装起来。

而 Rails用一句轻描淡写find_by就解决了所有的问题,而且,这个find_by_title方法还不是我实现的,Rails会替你自动实现。当我们需要有更多的查询条件时,只要一个一个附加上去就可以了。

Article.find_by_title_and_author("foo", "bar")

同样的事,如果放到Java里去做,还需要把前面说的事再做一遍,差别只是查询语句不一样。

虽然我说的是当年的场景,但时至今日,在这些简单问题上,很多使用Java的团队所付出的工作量并不比当年少。

从功能的角度说,这样的查询在功能上是完全一样的,但显然Rails程序员和Java程序员的工作量是天差地别的。这其中的差异就是不同的编程接口所造成的。

所以你看,一个好的接口设计会节省很多工作量,会减少犯错的几率。因为它会在背后帮你实现那些细节。

而设计不好的接口,则会把其中的细节暴露出来,让使用者参与其中。写程序库和写应用虽然都是写代码,但二者的要求确实相差极大。把细节暴露给所有人,显然是一个增加犯错几率的事情。

Rails的API接口给行业带来的另一个影响是,它让人们开始关注API的表达性。比如,每篇文章可以有多个评论,用Rails的方式写出来是这样的:

class Article < ApplicationRecord
  has_many :comments
  ...
end

而如果用传统Java风格,你写出来的代码,可能是这个样子的:

class Article {
  private List<Comment> comments;
  ...
}

很明显,“有多个”这种表示关系的语义用has_many表示更为直白,如果用List ,你是无法辨别它是一个属性,还是一个关系的。

Rails里面类似的代码有很多,包括我们前面提到的find_by。所以,如果你去读Rails写成的应用,会觉得代码的可读性要好得多。

由于Rails的蓬勃发展,人们也开始注意到好接口的重要性。Java后期的一些开源项目也开始向Rails学习。比如,使用Spring Data JPA的项目后,我们也可以写出类似Rails的代码。声明一对多的关系,可以这样写:

class Article {
  @OneToMany
  private List<Comment> comments;
  ...
}

而查询要定义一个接口,代码可以这样写:

interface ArticleRepository extends JpaRepository<Article, Long> {
  Article findByTitle(String title);
  Article findByTitleAndAuthor(String title, String author);
}

当你需要使用的时候,只要在服务里调用对应的接口即可。

class ArticleService {
  private ArticleRepository repository;
  ...
  public Article findByTitle(final String title) {
    return repository.findByTitile(title);
  }
}

显然,Java无法像Rails那样不声明方法就去调用,因为这是由Ruby的动态语言特性支持的,而Java这种编译型语言是做不到的。不过比起从前自己写SQL、做对象映射,已经减少了很多的工作量。

顺便说一下,Spring Data JPA之所以能够只声明接口,一个重要的原因就是它利用了Spring提供的基础设施,也就是上一讲提到的依赖注入。它帮你动态生成了一个类,不用你自己手工编写。

简单,表达性好,这就是Rails API的风格。

命令行接口

作为程序员,我们都知道自动化的重要性,但Rails 在“把命令行的接口和整个工程配套得浑然一体”这个方面做到了极致。Rails的自动化不仅会帮你做一些事情,更重要的是,它还把当前软件工程方面的最佳实践融合进去,这就是Rails的命令行风格。

如果要创建一个新项目,你会怎么做呢?使用Rails,这就是一个命令:

$ rails new article-app

这个命令执行的结果生成的不仅仅是源码,还有一些鼓励你去做的最佳实践,比如:

  • 它选择了Rake作为自动化管理的工具,生成了对应的Rakefile;
  • 它选择了RubyGem作为包管理的工具,生成了对应的Gemfile;
  • 为防止在不同的人在机器上执行命令的时间不同,导致对应的软件包有变动,生成了对应的Gemfile.lock,锁定了软件包的版本;
  • 把对数据库的改动变成了代码;
  • ……

而这仅仅是一个刚刚生成的工程,我们一行代码都没有写,它却已经可以运行了。

$ bin/rails server

这就启动了一个服务器,访问 http://localhost:3000/ 这个 URL,你就可以访问到一个页面。

如果你打算开始编写代码,你也可以让它帮你生成代码骨架。执行下面的命令,它会帮你生成一个controller类,生成对应的页面,甚至包括了对应的测试,这同样是一个鼓励测试的最佳实践。

$ bin/rails generate controller Welcome index

在Rails蓬勃发展的那个时代,人们努力探索着Web开发中各种优秀的做法,而在这个方面走在最前沿的就是Rails。所以,那个时候,我们经常会关注Rails的版本更新,看看又有哪些好的做法被融入其中。

Rails中那些优秀的实践逐步地被各种语言的框架学习着。语言编写者们在设计各种语言框架时,也都逐步借鉴了Rails中的那些优秀实践。比如,今天做Java开发,我们也会用到数据库迁移的工具,比如Flyway。

当然,另一个方面,即便到了今天,大部分项目的自动化整合程度也远远达不到Rails的高度,可能各方面的工具都有,但是如此浑然一体的开发体验,依然是Rails做得最好。

最后,你可能会问,Rails这么优秀,为什么今天却落伍了呢?

在Web开发领域,Rails可谓成也MVC,败也MVC。MVC是那个时代Web开发的主流,页面主要在服务端进行渲染。然而,后来风云突变,拜JavaScript虚拟机V8所赐,JavaScript能力越来越强,Node.js兴起,人们重新认识了JavaScirpt。它从边缘站到了舞台中心,各种组件层出不穷,前端页面的表现力大幅度增强。

Web开发的模式由原来的MVC,逐渐变成了前端提供页面,后端提供接口的方式。Java的一些框架和服务也逐步发展了起来,Spring系列也越来越强大,重新夺回了Web后端开发的关注。

总结时刻

今天,我们学习如何了解设计的第二部分:看接口。看接口的一个方法是找主线,看风格。先找到一条功能主线,对项目建立起结构性的了解。有了主线之后,再沿着主线把相关接口梳理出来。

查看接口,关键要看接口的风格,也就是项目作者引导人们怎样使用接口。在一个项目里,统一接口风格也是很重要的一个方面,所以,熟悉现有的接口风格,保持统一也是非常重要的。

我还介绍了一个曾经火爆的Web开发框架:Ruby on Rails。借着它的起步走文档,我给你介绍了它的一些接口,包括:

  • Web应用对外暴露的接口:REST API;
  • 程序员写程序时用到的接口:API;
  • 程序员在开发过程中用到的接口:命令行。

从Rails的接口设计中,我们可以看到,一个好的接口设计,无论是最佳实践的引入,抑或是API设计风格的引导,都可以帮助我们建立起良好的开发习惯。

当我们理解了模型和接口,接下来就该看实现了,这就是我们下一讲要讲的内容。

如果今天的内容你只能记住一件事,那请记住:理解一个项目的接口,先找主线,再看风格。

思考题

最后,我想请你来分享一下,你在哪个项目的设计中学到了一些好的开发习惯呢?欢迎在留言区分享你的经历。

感谢阅读,如果你觉得这一讲的内容对你有帮助的话,也欢迎把它分享给你的朋友。

精选留言

  • Hank_Yan

    2020-06-07 16:38:31

    找主线,看风格。找主线看文章,看风格看接口。从上到下,从整体到局部。不过这也是正常读源码的一个步骤。
    作者回复

    太喜欢你这个评论了,这就应该是正常的步骤啊,可是很多人不知道。

    2020-06-11 21:10:22

  • Jxin

    2020-06-05 00:42:41

    1.最早自学的就是ruby,要不是因为找不到工作,可能就做不成javaer了。论快速搭建一个web项目,至今依旧是ruby on rails。一个多小时从无到有搭建一个博客系统的时候,信心爆棚。

    2.本篇,明天得再看看。get不到点。只能理解风格应该是说设计偏好。至于主线,从ruby这个demo里没能get到。


    3.spring。兼容(老版本以及各种场景),开放(提供规范和基础工具,方便各种“实现”自己写插件接入spring),与时俱进(springboot的推出,算得上破而后立),追求卓越(在迭代中改变接口命名,只为让原本达意的命名更达意)
    作者回复

    Ruby 成于 Rails,也败于 Rails。

    2020-06-22 10:33:59

  • 王智

    2020-06-06 14:45:55

    Ruby on Rails这个设计在当时感觉很超前,也不知道现在的SpringBoot是不是借鉴了,感觉从SpringBoot上能看倒Ruby on Rails的身影, SpringBoot的约定大于配置,还SpringBoot把命简洁的页面,勾选之后就可以创建一个简单的SpringBoot项目等等,Ruby on Rails的设计在现在看来可能确实没什么,但在当时感觉这个设计就太超前了.
    作者回复

    你说得很对,Rails超前是全方位的,今天看来,很多东西都影响了全行业。

    2020-06-06 21:07:15

  • 捞鱼的搬砖奇

    2020-06-05 11:43:37

    在 Spring 的源码中 接口 -> 抽象类->实现类,如 BeanDefinition -> AbstractBeanDefinition -> RootBeanDefinition。这样的设计风格。顶层接口规定定义,抽象类提供部分实现。用户需要扩展,可以选择从抽象类进行扩展,或从接口扩展。
    作者回复

    上一讲是 Spring,这一讲是 Ruby on Rails,这个评论让我有一种走错片场的感觉。:)

    2020-06-05 14:19:39

  • 行与修

    2020-06-07 09:26:01

    好的设计要多从使用者角度考虑,是否有助于释放程序员精力,是否易用,是否有良好的可读性和可扩展性。自己先用,并在开始时就设定好边界,即使先只在一点上有所突破也比全面开花哪哪不灵要好。
    作者回复

    这个我完全同意!你说的单点突破实际上就是 MVP 的思路。

    2020-06-18 09:33:51

  • 六一王

    2020-06-08 01:10:45

    接触的第一个框架就是 rails,用起来确实很爽,今天看到此篇文章,让我对它更有敬意,而不会因为现在不就行,或者性能不够好,就看不起它,没有东西是绝对的好,或者绝对不好,今天全是对这个道理有更深层的理解了。
    我之后学习了JavaScript,转了前端,学习 Vue 框架,它使用虚拟dom将组件高度抽象化,使页面组件可以多端运行,比如node端,如此可以前后端同构,解决单页面应用的劣势,而保留其优势。
    模块如何拆解然后又如何组装,逻辑清晰。
    提供了很多全局组件和生命周期函数,以及其他方法,这些就是给程序员提供的接口,可以让我们在特定的场景让你更加关注功能需求的实现,而不是代码的实现细节,会自动帮你做好很多事情。
    并且使用了很多高阶函数,化繁为简,最终返回一个干净的只有核心逻辑的函数。即大量使用了函数式编程。
    学习的过程就是写一段简单的,特定场景代码,然后进行断点测试,看看源码中主要会走那些流程。然后大概就有了一条小主线。
    好的框架理解起来应该是容易的,清晰的。而那些不假思索写出来的面向过程的代码,如果不一行一行去看的话,就不知道问题出在哪里,甚至以后回过来看,都不知道自己是怎么写出这种代码的,就像写正则表达式一样,写完之后,自己都看不懂了。
    作者回复

    感谢你的分享,丰富了更多的内容。

    2020-06-17 11:48:30

  • 西西弗与卡夫卡

    2020-06-05 21:17:16

    怀念RoR,起初用过,后来因为性能,我们都替换成Java了
    作者回复

    曾经沧海

    2020-06-06 11:07:22

  • qinsi

    2020-06-05 16:33:27

    最早接触BDD是cucumber,当时觉得简直是magic。整个RoR框架也到处给人magic的感觉。但是软件开发不应该有magic,否则容易失控。RoR式微部分原因可能也是Ruby过于灵活了,印象中RoR有些严重的安全漏洞就是由此引发。此外Ruby的执行效率也逐渐落后于时代了。近年来有Crystal语言借由llvm复兴Ruby,加入了很多现代语言的特性,希望能有好结果。
    作者回复

    这是两个方面,使用者和开发者。使用者的角度,那是简单的,开发者的角度,需要理解那些 Magic。

    Ruby 有些问题其实动态语言的问题,在开发大型应用上,没有类型是一个很伤的地方。

    执行效率其实与使用程度是相关的,只要有更多的人在用,就会有更多的人来优化,如果没有人用,优化的动力自然就不强了。

    2020-06-18 09:38:29

  • jakimli

    2020-06-28 17:19:33

    问个问题,如果说rails落伍的原因是因为Javascript,为什么没有演变成 javascript + rails后端服务这样的组合呢?
    作者回复

    在加餐里讲了 JavaScript 的兴起,实际上,Node.js 并没有真正意义上成为后端开发的主力,却促进了前端的发展,让前后端分离了,结果,后端借此兴起的是 Java。

    Rails 归根结底是有硬伤的,性能差。在没有了 MVC 的加持之后,Java 就重新回来了。

    2020-06-29 13:50:49

  • 阳仔

    2020-06-05 08:46:24

    理解软件中的接口设计,要抓住主线,可以从文档开始入手,了解软件设计者的风格品味,看看作者希望我们是如何使用这些接口的。
    我没用过Ruby,但是通过分析之后,其实它的接口设计中,整合了许多极佳的工程实践,提高编码效率,解放生产力,这些思想在软件设计的时候是可以学习和参考的
    作者回复

    能够把开发效率提高,也是一大推动力。

    2020-06-22 10:34:31

  • escray

    2020-06-05 09:35:41

    “优雅的编程接口,顺畅的开发过程”,虽然我也非常的喜欢 Ruby on Rails,却没有办法像老师这样精辟的总结提升。

    给我的感觉,RoR 在设计的时候非常的体贴程序员,并且采用了一大堆优秀的设计范式。

    看了文中对于 Rails 接口的分析,感觉更喜欢 Rails 了,可惜的确如同留言里面 @Jxin 说的那样,工作不好找,另外薪水不高。

    如果按照开发模式的变迁,那么现在是不是应该学习 Deno ? 请老师推荐一个比较有潜力的语言或者框架,Go 或者 TypeScript ?
    作者回复

    很快就轮到讲程序设计语言了,简言之,多学点。

    2020-06-05 11:57:56

  • Y024

    2020-09-25 23:23:26

    今天熊大在群里说了“没用过 rails 就不足以聊 DDD”、“我以为玩玩 rails 是程序员的基本要求呢”,同时也推荐了一本书《Agile Web Development with Rails》。特地再来复习复习。
  • 舀点米 | Titus Mi

    2020-06-19 09:15:40

    那python的Django是不是也有rails的问题?那flask呢?java的spring boot是更好的实践吗?
    另外rails应该也可以使用前后端分离吗?老师是否还可以再分享下,spring具体是如何越来越强大,使得rails落伍?spring和node如何联手超越rails的?
    作者回复

    从工程实践的角度看,Rails 是最好的,很多后来者抄袭了 Rails,比如,Django。Spring Boot 最近这些年进步很大,但依然不如 Rails。Spring Boot 其实就是把 Spring 这么多年积累的组件合到了一起,再加上嵌入式服务器的发展,大幅度地降低了开发难度。

    编程模型从 MVC 转向了 REST 服务,是一个重大的契机。Rails 自身的执行效率本来就是一个硬伤。Spring Boot 可以说抓住了新一波的浪潮,让 Java 重新回到了巅峰上。

    之所以 MVC 转向了 REST,要拜 Node.js 所赐,让前端有了大发展,这段分析在程序设计语言的加餐中。

    2020-06-24 19:04:19

  • 蓝士钦

    2020-06-11 23:22:34

    关于接口想请教一下老师,我们平时在公司内部的二方库,是不是应该拆分成两个jar包,分为api模型包和一个实现包。对于大多数开发业务我们只关心一个系统的模型以及提供什么样的功能接口。拆分成不同包可以根据需要引入不同的实现包,而api和模型是共用的。 比如最近有个项目需要把查数据湖的报表改成差数据库,如果封装的足够好应该只要替换实现就可以不改一行代码达到目标。
    作者回复

    这其实是打包原则的事。从理论上说,分成两个是最好,只是实际情况很多人分不了那么干净。

    2020-06-16 07:34:52

  • 6点无痛早起学习的和尚

    2023-08-23 07:01:50

    这一章的内容,读起来并没有 get 到内容,先继续向下看
  • 努力努力再努力

    2022-11-23 18:57:52

    不知道是不是3年经验问题……感觉好难get到老师想表达的设计点在哪
  • ifelse

    2022-05-09 12:40:09

    理解一个项目的接口,先找主线,再看风格。--记下来
  • William Ning

    2022-04-23 22:24:32

    现在对模型的理解,依然是很懵懂~
  • 阿彪

    2021-12-15 08:39:01

    模型是指MVC? 我还以为是我们建的各种自定义业务模型
  • 林铭铭

    2021-04-23 17:43:05

    找主线识别整体流程,看风格识别编程习惯。