好的代码 往往是用来解决人的问题


前言

大家好这里是阳九. 一个普通的转行全栈码农。

今天我们不聊技术,我们来聊聊人,代码与人的关系。

作为一个小语种出身的程序员, 我对编程这件事情的理解从一开始就是

写出一篇文章 让计算机能读懂,

而后在工作的过程中我逐渐意识到, 阅读和理解代码花费的时间比写代码更长, 于是乎编程变成了

写出一篇优美的文章, 让计算机和人都能读懂

作为一个在职程序员, 我看到很多同事往往忽略了与人交互的能力, 我们的工作说简单一点是"编程", 但更深层次地说, 我们是在进行 "人机交互", 用户使用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) => {...}
  }
  • 大体的重构思路是, 代码应当分为三层,
  1. 最底层是大量的细节api的调用, 将他们拆分为多个短函数, 起一个好名字 - 也就是省略的部分
  2. 中间层是多个短函数的组合, 用于给人读 - 我把它称为(可读层)
  3. 最顶层是函数的高级抽象, 用于告诉你这个函数大概做了什么- 也就是整个函数的名称
  • 对于这份重构,实际上就是想办法添加了一层可读层,让人更好的理解函数做了什么,而不是看底层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)是一个非常著名的设计原则。定义很简单,"每个软件模块应该只有一个改变的理由" ,其中的关键点是要找到"修改模块的原因是什么",

  • 但是在实际开发中,人才是导致程序修改的罪魁祸首

举个很简单的例子, 一个员工档案类, 可以支持两种操作

  1. 更新员工信息
  2. 打印员工信息

我们很简单的就发现了它不符合SRP原则, 但如果产品经理需要如下逻辑

  1. 电话号码应当增加校验逻辑
  2. 姓名的部分字体太小,需要加大

这个时候你是否还能在工期限制的情况下, 严格依照SRP原则去编写代码?

所以有人重新定义了该原则

"要求更改的是人。你不想把许多不同的人出于不同的原因而把代码混在一起,从而使这些人或你自己感到困惑"。--- 单一责任原则

再谈微服务架构

  • 很多人在讨论微服务架构的时候, 往往只关注技术本身,却忽略了微服务架构与人之间的关系
  • 微服务架构对于"人"的优点在于:
  1. 将单体系统拆分成独立的微服务后,不同模块之间的界限会变得更加清晰
  2. 比起数百人维护单体系统, 拆分为多个小组维护独立的微服务有更高的运营效率
  3. 减轻部署人员的工作量 等等

我们看到如下微服务拆分方案,

  • 单体电商应用
  • 拆分为

从以下角度试想一下,上述微服务拆分方案的优点

  • 如果同时有使用不同语言,不同业务框架的程序员同时存在于组内, 是否能让他们顺利的合作?
  • 如果大家同时改一份仓库代码, 会造成多少冲突? 上线前为了合并代码需要花费多少功夫?
  • 如果一个人员离职, 是否能快速补上合适的维护人员?
  • ......

领域驱动设计

  • 在DDD领域设计中, 最重要的就是与领域专家合作,构造相应的领域模型。
  • 而其中最重要的就是,利用大量最佳实践和标准模式在开发团队中形成统一的交流语言,不但要重构代码,而且要重构代码底层模型
  • 这里的统一交流语言, 就是从专业术语,代码结构的角度, 整合形成一份新的人员交互方案, 比如如何将硬件设计 PCB, 引脚设计等硬件知识通过纯软件的方式展现出来,供给硬件专家使用

结尾

所以对于代码来说, 大部分设计和范式, 往往都需要同时兼顾机器和人, 程序员是一个需要持续与外界对话的职业, 并不是大家给出的 "不善言辞的码农" 这样的标签。

写代码更像是写作, 只不过评判的标准与文学不同罢了。

相关推荐
WaaTong1 小时前
《重学Java设计模式》之 单例模式
java·单例模式·设计模式
WaaTong3 小时前
《重学Java设计模式》之 原型模式
java·设计模式·原型模式
霁月风3 小时前
设计模式——观察者模式
c++·观察者模式·设计模式
暗黑起源喵6 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
wrx繁星点点13 小时前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式
金池尽干15 小时前
设计模式之——观察者模式
观察者模式·设计模式
也无晴也无风雨15 小时前
代码中的设计模式-策略模式
设计模式·bash·策略模式
捕鲸叉1 天前
MVC(Model-View-Controller)模式概述
开发语言·c++·设计模式
wrx繁星点点1 天前
享元模式:高效管理共享对象的设计模式
java·开发语言·spring·设计模式·maven·intellij-idea·享元模式
凉辰1 天前
设计模式 策略模式 场景Vue (技术提升)
vue.js·设计模式·策略模式