前言
大家好这里是阳九. 一个普通的转行全栈码农。
今天我们不聊技术,我们来聊聊人,代码与人的关系。
作为一个小语种出身的程序员, 我对编程
这件事情的理解从一开始就是
写出一篇文章 让计算机能读懂
,
而后在工作的过程中我逐渐意识到, 阅读和理解代码花费的时间比写代码更长, 于是乎编程变成了
写出一篇优美的文章, 让计算机和人都能读懂
作为一个在职程序员, 我看到很多同事往往忽略了与人交互的能力, 我们的工作说简单一点是"编程", 但更深层次地说, 我们是在进行 "人机交互", 用户使用UI与计算机交互, 程序员使用代码与计算机交互。
浅谈代码重构
唯有写出人类容易理解的代码,才是优秀的程序员 ---《重构-改善既有代码设计》
-
对重构手法比较熟悉的朋友应该了解, 除了写好完善的测试用例, 重构的早期重要步骤就是
为原函数添加足够多的结构, 以便更好地理解它
。 -
给出一个我曾经写过的重构例子,把一份杂乱无章的代码整理了一下 (基础重构案例,非新手请跳过)
jsx
class Page extends React.Component {
constructor() { }
uploadUpdate = () => {
const { packageFile, edition } = this.state;
const body = new FormData();
body.append('file', packageFile);
let config = {
onUploadProgress: progressEvent => {
if (!this.state.uploadingPackage) {
return;
}
let loaded = progressEvent.loaded;
let total = progressEvent.total;
let percent = ((loaded / total) * 100) | 0;
let now = Date.now();
let bytesPerSecond =
(1000 * (loaded - this.lastRecordLoaded)) / (now - this.lastRecordTime);
let remainUploadSeconds = (total - loaded) / bytesPerSecond;
this.lastRecordTime = now;
this.lastRecordLoaded = loaded;
this.setState({
percent,
bytesPerSecond,
remainUploadSeconds
});
},
cancelToken: new CancelToken(cancel => {
this._cancelUpload = cancel;
})
};
this.setState({ percent: 0, uploadingPackage: true });
this.lastRecordLoaded = 0;
this.lastRecordTime = Date.now();
this._isCanceled = false;
body.append('update_type', 'system');
let api = uploadClanPackage;
api(body, config)
.then(res => {
const pkginfo = _.get(res, 'data.data.result.pkginfo', {});
const pkgVersion = _.get(pkginfo, 'version', '');
const updatedSameVersionPkgAtCluster = edition === pkgVersion;
const verify =
_.get(res.data.data, 'result.verify') === 'success' ||
(_.get(res.data.data, 'result.master_ret.verify') === 'success' &&
edition !== pkgVersion);
this.setState({
verify,
...
});
})
.catch(() => {
if (!this._isCanceled) {
this.setState({
uploadingPackage: false,
uploadModalVisible: false,
verifyModalVisible: true,
verify: false
});
} else {
this.setState({ uploadingPackage: false });
}
});
};
}
- 重构后
jsx
class Page extends React.Component {
constructor() { }
uploadUpdatePackageFile = () => {
initUploadProgressBar()
const body = initFormData()
const config = initApiConfig()
API.uploadClanPackage(body, config)
.then(onUploadSuccess)
.catch(onUploadFail);
// 初始化进度条
const initUploadProgressBar = () => {...}
// 初始化formData
const initFormData = () => {...}
// 构造api参数
const initApiConfig = () => {...}
// 单次渲染进度条
const renderUploadProgressBar = progressEvent => {...}
// 上传成功
const onUploadSuccess = res => {...}
// 上传失败
const onUploadFail = () => {...}
// 校验文件及其版本
const validateUploadPkg = (res) => {...}
}
- 大体的重构思路是, 代码应当分为三层,
- 最底层是大量的细节api的调用, 将他们拆分为多个短函数, 起一个好名字 - 也就是省略的部分
- 中间层是多个短函数的组合, 用于给人读 - 我把它称为(可读层)
- 最顶层是函数的高级抽象, 用于告诉你这个函数大概做了什么- 也就是整个函数的名称
-
对于这份重构,实际上就是想办法添加了一层可读层,让人更好的理解函数做了什么,而不是看底层API
-
代码行数增加了, 可读性也增高了, 在代码里总是有一部分的文字是给人看的, 一部分是给机器看的, 一份优秀的软件应当以可演化,意义明确为贵
-
而可读性的提高,结构的清晰,往往能让人们对真正的性能点做出进一步优化。所以本质上, 重构的目的, 一个是更方便的进行性能优化, 另一个重要的就是提高可读性, 让人们更快理解它。
-
技术固然重要,但是我们总是能听到 "技术是为业务服务的" 这样的话, 而与之相对的则可以是 "架构代码是为人服务的"
浅谈设计模式
策略模式应该是最为简单且实用的模式之一, 曾经对某个业务使用了策略模式重构,比如产品经理设计了 "订单数可以超过最大货物数量的10%" 这条规则 (因为总有人在最后一刻取消订单)
首先我们可以写下这段代码
js
function getBooking(){
let maxBooking = storeCargoSize * 1.1 // 最大订单数是货物数量的110%
if(bookedCargoSize>maxBooking){ // 超过最大订单数返回-1
return -1
} ...
}
但我们发现, 这条重要的业务规则被隐藏在代码里, 除了编写他的程序员和制定规则的经理,其他人都不知道。 而产品经理和客户自然是不会看代码的, 所以我们需要将这条规则单独抽离出来, 让更多的人知道它,
- 对于程序员来说, 一个绝佳的方式就是将重要的策略都封装为对象,然后统一放在一个文件内, 也更加方便修改, 也就出现了策略模式
js
function getBooking() {
let maxBooking = new BookStrategy().maxBooking() // 业务代码中使用该策略
if (bookedCargoSize > maxBooking) {
return -1
}
}
- 这里单独维护一组订单策略算法, 写好注释
js
class BookStrategy {
//最大订货数量为110%
maxBooking() {
return storeCargoSize * 1.1
}
//购物车内未支付的订单数最大为99
unpaidBookCount() {
return 99
}
...
}
- 为了让产品经理更好理解 我们应当再提供一份对应的文档,并交给程序员和经理共同维护
md
## 订单策略
- 最大订单数是货物数量的110%
- 购物车内未支付的订单数最大为99
...其他
再谈SRP - 单一职责原则
-
"单一责任原则"(Single Responsibility Principle)是一个非常著名的设计原则。定义很简单,"每个软件模块应该只有一个改变的理由" ,其中的关键点是要找到"修改模块的原因是什么",
-
但是在实际开发中,人才是导致程序修改的罪魁祸首
举个很简单的例子, 一个员工档案类, 可以支持两种操作
- 更新员工信息
- 打印员工信息
我们很简单的就发现了它不符合SRP原则, 但如果产品经理需要如下逻辑
- 电话号码应当增加校验逻辑
- 姓名的部分字体太小,需要加大
这个时候你是否还能在工期限制的情况下, 严格依照SRP原则去编写代码?
所以有人重新定义了该原则
"要求更改的是人。你不想把许多不同的人出于不同的原因而把代码混在一起,从而使这些人或你自己感到困惑"。--- 单一责任原则
再谈微服务架构
- 很多人在讨论微服务架构的时候, 往往只关注技术本身,却忽略了微服务架构与人之间的关系
- 微服务架构对于"人"的优点在于:
- 将单体系统拆分成独立的微服务后,不同模块之间的界限会变得更加清晰
- 比起数百人维护单体系统, 拆分为多个小组维护独立的微服务有更高的运营效率
- 减轻部署人员的工作量 等等
我们看到如下微服务拆分方案,
- 单体电商应用
- 拆分为
从以下角度试想一下,上述微服务拆分方案的优点
- 如果同时有使用不同语言,不同业务框架的程序员同时存在于组内, 是否能让他们顺利的合作?
- 如果大家同时改一份仓库代码, 会造成多少冲突? 上线前为了合并代码需要花费多少功夫?
- 如果一个人员离职, 是否能快速补上合适的维护人员?
- ......
领域驱动设计
- 在DDD领域设计中, 最重要的就是与领域专家合作,构造相应的领域模型。
- 而其中最重要的就是,利用大量最佳实践和标准模式在开发团队中形成统一的交流语言,不但要重构代码,而且要重构代码底层模型
- 这里的统一交流语言, 就是从专业术语,代码结构的角度, 整合形成一份新的人员交互方案, 比如如何将硬件设计 PCB, 引脚设计等硬件知识通过纯软件的方式展现出来,供给硬件专家使用
结尾
所以对于代码来说, 大部分设计和范式, 往往都需要同时兼顾机器和人, 程序员是一个需要持续与外界对话的职业, 并不是大家给出的 "不善言辞的码农" 这样的标签。
写代码更像是写作, 只不过评判的标准与文学不同罢了。