0%

Preface

在之前的博文中我们介绍了 Git Flow 分支模型,正如文中所说,Git Flow 偏向于控制管理,使用了较多的分支,流程颇为复杂。大量的团队在实践过程中也遇到了颇多问题,其中大部分来自长期存在的分支。随着软件开发模型的演进,GitHub Flow、Trunk Based Development 等模型也应运而生,也已被 Google、Facebook、TW 等企业实践。本文主要介绍 TBD 模型。

Git Flow的问题

  • 合并冲突,合并冲突在使用 Git Flow 是非常常见的。原因很简单:如果你有多个并行功能分支,他们长时间存在,那么很可能代码库的相同部分在两个功能分支中被分别更改。合并冲突不仅对于需要手动解决的开发人员来说是令人沮丧的,也增加了在代码中破坏某些功能的风险,因为当你不得不决定使用哪个版本代码时,很容易犯错。

  • 功能分离,在合并到同一个分支之前,你不能测试两个功能的组合。当你在单独的分支中开发几天甚至几周的功能时,当合并回主分支后,可能也会发生两个功能的相互作用影响了你的代码。

  • 并没有做到持续交付,在 Git Flow 分支模型下,发布是非常有计划的,一个 feature 必须要经过一系列步骤才能到达生产环境,在时间上平均一个 feature 都要等待 两周时间才能长线,这样的等待并非是需求上的“按计划发布”,而是从技术上就造成了发布瓶颈,显然难以达到持续交付的要求。

  • 与持续集成相悖,你会发现,在坚持持续集成实践的情况下,feature 分支是一件非常矛盾的事情。持续集成鼓励更加频繁的代码集成和交互,让冲突越早解决越好。feature 分支的代码隔离策略却在尽可能推迟代码的集成。

GitHub Flow

GitHub Flow 是一个更轻量级的软件开发模型,示意图如下。它摒弃了 Git Flow 中繁杂的分支,只保留一个主分支 master。开发新功能时从 master 分支上拉取 feature 分支,开发完成后发起 Pull-Request,小组内进行评审和反馈,此时也进行 Code Review。测试通过后合并回主分支。

GitHub-Flow2-1

相比于 Git Flow,这种方式因为省去了一些分支而降低了复杂度,同时也更符合持续集成的思想,以一张故事卡为集成的最小单位,相对来说集成的周期短,反馈的速度也快,能够及早的遇到问题并及早解决。

顺着持续集成的思想,如果我们把 GitHub Flow 分支模型做得再极致一点,我们不要 feature 分支,或者把 feature 分支只留在本地;不需要使用 Pull-Request 而是直接 Push 到远程 master 分支,我们就做到了 Trunk based Development。

TBD

阅读全文 »

概念

你的本地仓库由 Git 维护的三棵「树」组成。第一个是你的工作目录(Working dir),它持有实际文件,即你所见的;第二个是缓存区(Stage or Index),它像个缓存区域,临时保存你的改动;第三个是提交历史(Commit history),包含的 HEAD 指针指向你最近一次 commit 的引用。关于工作区和缓存区概念可参考廖雪峰的Git教程

687474703a2f2f6d61726b6c6f6461746f2e6769746875622e696f2f76697375616c2d6769742d67756964652f62617369632d75736167652e737667-1

上面的四条命令在工作目录、stage 缓存(也叫做索引)和 commit 历史之间复制文件。

  • git add files 把工作目录中的文件加入 stage 缓存。
  • git commit 把 stage 缓存生成一次 commit,并加入 commit 历史。
  • git reset -- files 撤销最后一次 git add files,你也可以用 git reset 撤销所有 stage 缓存文件。
  • git checkout -- files 把文件从 stage 缓存复制到工作目录,用来丢弃本地修改。

merge

git merge 命令把不同分支合并起来。合并前,索引必须和当前提交相同。git merge other 用于将 other 分支上的提交合并到当前分支。

如果另一个分支是当前提交的祖父节点,那么合并命令将什么也不做。另一种情况是如果当前提交是另一个分支的祖父节点,就导致 fast-forward 合并,HEAD 指针只是简单的移动。

687474703a2f2f6d61726b6c6f6461746f2e6769746875622e696f2f76697375616c2d6769742d67756964652f6d657267652d66662e737667

否则就是一次真正的合并。默认把当前提交(ed489 如下所示)和另一个提交(_33104_)以及他们的共同祖父节点(_b325c_)进行一次三方合并。结果是先保存当前目录和索引,然后和父节点 33104 一起做一次新提交

阅读全文 »

前言

目前常用的 Linux 系统和 OS X 系统的默认 Shell 都是 bash,但是真正强大的 Shell 是深藏不露的 zsh, 这货绝对是马车中的跑车,跑车中的飞行车,史称『终极 Shell』,但是由于配置过于复杂,所以初期无人问津,很多人跑过来看看 zsh 的配置指南,什么都不说转身就走了。直到有一天,国外某位大牛开源出一个能够让你快速上手的 zsh 项目,叫做『oh my zsh』,使得 zsh 逐渐流行起来。

ZSH

安装 zsh。

1
brew install zsh

安装完后在 Terminal 的设置中设置默认使用 zsh。

8F9622E7-901E-46F0-94D7-B5E7AEE04C08

Oh My Zsh

oh-my-zsh 是最为流行的 zsh 配置文件,提供了完善的插件体系,相关的文件在~/.oh-my-zsh/plugins目录下。GitHub 链接

安装 oh-my-zsh:

阅读全文 »

前言

基于我的上一篇博客:Java 异常处理中的观点,我编写了一个简单的 Web 案例,包括自定义异常类,在合适的层面处理,以及遇到的由于代码 Bug 抛出运行时异常的情形。示例代码已经上传至 GitHub,包含基于 Spring Boot 的 Java 后端代码以及基于 Vue.js 的前端代码两个部分。

我们的演示案例有两个功能,获取用户列表以及注册新用户,界面如下所示:

102BDB82-6CAD-4C3F-A5EC-5F1052F3A337

后端实现

先看一下我们领域模型 User,为简化代码未使用数据库,而是模拟数据库的自增主键自动生成 userId。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Data
public class User {

private static int userIdGenerateKey = 1;

private Integer userId;
private String username;
private String phone;
private Integer age;

public User() {
}

User(String username, String phone, Integer age) {
this.userId = User.userIdGenerateKey++;
this.username = username;
this.phone = phone;
this.age = age;
}

User(User userWithoutId) {
this(userWithoutId.username, userWithoutId.phone, userWithoutId.age);
}
}

在资源库UserRepository一层使用List直接在内存中存储用户,并初始化了两条用户信息。对外提供users()返回所有用户以及addUser()添加一个新用户两个接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Repository
public class UserRepository {

private List<User> users = new ArrayList<>();

public UserRepository() {
users.add(new User("Jack", "13500000000", 18));
users.add(new User("Tom", "13500000001", 19));
}

public List<User> users() {
return users;
}

public void addUser(User user) {
users.add(user);
}
}

考虑到在业务功能为获取用户列表和注册新用户,而注册功能还可能包括发送验证邮件等之类的功能,所以有必要将其提取至 Service 层提供一个register()接口。目前只是单纯的调用资源库的addUser()添加一个新的用户。

阅读全文 »

前言

一直以来,Java 的异常处理可能是每个 Java 程序员都需要面对和经历的难题之一。何时该抛出异常,该抛出何种异常,需不需要自定义异常类,何时又该捕获异常并处理,相信每一个 Java 程序员或多或少都有这样的疑问。最近也是觉得项目需要统一规范的异常处理,所以花了些时间研读 Thinking in Java、《阿里巴巴 Java 开发手册》以及前辈们总结的优质文章,归纳了一些自认为比较关键的部分,希望有助于大家更好的设计如何处理异常。关键还是需要大量优秀源码的阅读经验。

异常的设计初衷是将运行时产生的错误信息通过某种方式传递给某个接收者 —— 该接收者将知道如何正确的处理这个问题。Java 使用异常来提供一致的错误报告模型,使得构件能够与客户端代码可靠地沟通问题。实际上,异常处理的一个重要目标就是把错误处理的代码同错误发生的地点分离,使得你在某处专注于要完成的事,而在另一处处理错误。既分离了主干代码和错误处理逻辑,又可以重用错误处理代码。—— Thinking in Java

上面所说的接受者其实分为两类,一类是开发人员,另一类是使用用户。所以,实际上异常设计出来:

  1. 帮助开发人员定位错误,修复代码漏洞。
  2. 反馈给客户端用户,比如表单的输入值非法,让他得以更正错误。

异常分类

Java 将异常分为两个大类:非检查型异常(Unchecked Exception),也称非受控异常。以及检查型异常(Checked Exception),也称受控异常。这两者的区别在于:

  1. Check Exception 编译器会做强制检查,必须使用try...catch捕获或者throws在方法签名处申明,否则编译不通过。
  2. Unchecked Exception 发生在运行期,具有不确定性,通常是由于程序的逻辑问题所引起的,所以在程序设计中我们需要考虑周全,尽量通过提前预检避免这类异常。

Java 中异常的继承结构如下图所示:

v2-2cb1558f17876f329804fcced62661ef_1200x500

阅读全文 »