从前,我以为:“好的代码像首诗,烂的代码像坨屎。”

但关于代码质量的好坏、阅览性的好坏,却没有一个有效的衡量标准。每个人心中的一杆秤,是不同的。

那么多年过去了,仍然无法去统一一切人的编程思想。而什么样的代码,才是好的代码,渐渐地,在心中有了一部分标准的答案。

在做一个项目时,经过以下几个方面因素来考虑代码架构的规划:

  • 进步开发的人效,不管是单枪匹马的项目,仍是团队协作的。在造轮子的时分尤其需求留意,轮子造得好或许用得好,能够事半功倍
  • 代码具有可扩展性可移植性。由于许多实践的原因,大部分的需求是不明晰的技能选型是不固定的。所以开始的规划很关键,直接影响了后续的迭代保护成本
  • 要有大局观。条条大路通罗马,异曲同工。同一种问题,或许有多种解决计划。学会变通,并在项目中灵敏运用。在做挑选的时分,权衡取舍,不能捡了芝麻,丢了西瓜。不需求在某一个点上捉住不放,而是需求德智体全面发展。

不管是在一个项目中,仍是在一个人的开发生涯中,慢慢地堆集沉积下来,应该从代码中体现出编写者的思想来,融入代码魂灵。而不是东一榔头,西一木棒,没有任何的特征;也不是某一方面深钻到了极致,像偏科严重,疏忽了其他的方面。

关于个人来说,终究应当变成“一精多专”,在某一个中心范畴规模称为领袖,在其他各个方面均有涉猎。关于项目来说,终究应当变成,输出中心技能价值,产品各方面挑不出太大缺点。

最佳实践

没有银弹。

一切的最佳实践或许都是阶段性的,或许针对某个范畴规模内的。不必故意魔化某几类技能、言语、东西、计划之类。

写代码从新手到入门,却有必要需求养成良好的习气。比方如下几点,是不管在任何言语、项目中,都能够推行的:

  • EditorConfig: 用户束缚 IDE 中各类型文件的字符集、缩进风格、换行风格等
  • Git Config:大小写灵敏、换行风格、no-ff 办法提交等一些通用配置习气
  • 代码标准及美化插件:这里以 js/ts 为例,推荐 ESLint 代码标准插件和 Prettier 代码美化插件配合运用。在各项目初始化的时分配置规则
  • 搭建测验结构。这算是初窥门径的最佳判别标准,不会运用 TDD(单元测验驱动开发)/BDD (行为测验驱动开发) 之前,不算会写代码。测验也是用来衡量代码质量、代码运转功率等一系列的前提根底。

另外需求明晰说明一点:在没有覆盖测验用例之前,一切的安全质量都是空谈。

与时俱进,坚持学习者的心态即可。主张是关注新技能、新结构及言语的新特性,并测验去用一用。当这些新的技能、计划老练或许能够稳定时,运用到已有项目中进行优化。要了解一点,再好的东西,总有被淘汰的一天(就比方我学习的第三个言语 Pascal,包括它母公司及产品 Delphi)。

以下要具体展开的几个方面互为相关,有或许会有堆叠。必读代码的可保护性高,那么代码开发功率不会低到哪里,代码的阅览性也是。

代码可保护性

常见的问题:

  • 是否具有扩展才能,比方增加一个新的参数,削减一个参数时,对其他地方代码的影响规模是否过大
  • 代码是否能够便利地运用和注入到其他需求的地方,许多时分,封装的代码大部分没有复用的空间,或许,用起来很费事,以至于项目中会有许多相似的、重复的代码
  • 代码过度封装,这个问题不只影响了代码的可保护性,也会影响代码质量、代码功率、代码阅览性

扩展才能

先从一段简略的代码开始:

function func1(arg1 = 'sth') {
 // ...
 return 'sth';
}
​
function func2(args) {
 const { arg1 = 'sth' } = args || {};
 // ...
 return 'sth';
}

如同都没什么问题。那么,杂乱一点:

function func1(arg1 = 'sth1', arg2 = false, arg3 = 3) {
 // ...
 return 'sth';
}
​
function func2(args) {
 const { arg1 = 'sth', arg2 = false, arg3= 3 } = args || {};
 // ...
 return 'sth';
}

这时分,就能够发现一些端倪,比方其他都用默认值,arg310

func1('sth', false, 10);
​
func2({ arg3 = 10 });

一起,在不凭借第三方 IDE 插件状况下,func2 的代码阅览性也比 func1 高许多。这样,也愈加便利进行 Code Review。

过度封装

接下来再说一个例子:

/**
 * 时刻
 */
export type Time = {
  [value]: moment.Moment
}
​
/**
 * 增加一天
 */
export function addDay(a: Time): Time {
 return {
   [value]: a[value].clone().add(1, "d"),
  }
}
/**
 * 削减一天
 */
export function subDay(a: Time): Time {
 return {
   [value]: a[value].clone().subtract(1, "d"),
  }
}
  • Moment.js 中已经封装好了对应的办法,自身用起来已经满意便利
  • 加一天,减一天这种办法不行灵敏,假如要加加五天或许加一周呢?

便利运用和便利注入

仍然是对上面的办法进行衍生:

/**
 * 格式化
 */
export function format(a: Time, 办法: string): string {
 return a[value].format(办法)
}

此刻,已经有三个办法名了 formataddDaysubDay,假如 IDE 有引入提示的话,已经需求记住 3 个了。一起,这里是时刻的格式化,办法名叫 format。后边或许还会有 SQL 语句的格式化、GraphQL 语句的格式化等等叫 format 的重名办法。在挑选和运用的时分,就会费事。

假如以 DateTime 封装为例:

/**
 * 时刻
 */
export class Time {
 #value: moment.Moment
 // 看状况,这里其实答应给 any 的话,关于运用该 Class 开发的人来说是一件十分省心省力的事情
 constructor(a?: moment.MomentInput) {
  // 只需求在构建器里做好判别和反常的抛出即可
  const _data = moment(a)
  if (isNaN(_data.unix())) throw new Error(`输入时刻无法解析: ${a}`)
  this.#value = _data
  }
 // 能够注入验证器,验证输入参数是否为合法类型
 // @validatePattern()
 format(pattern = "YYYY-MM-DD") {
  return this.#value.format(pattern)
  }
 toString() {
  return this.#value.toString()
  }
 get value() {
  return this.#value.unix()
  }
}
const demo = new Time(new Date())
console.log(demo.format())
// 2023-08-31
console.log(demo.toString())
// Thu Aug 31 2023 15:52:30 GMT+0800
console.log(demo.value)
// 1693468350
// console.log(demo.#value)
// 报错

运用类和接口完结面向目标编程

在 TypeScript 中,运用类和接口能够完结面向目标编程的封装、继承和多态特性,进步代码的可保护性和可扩展性。

// 运用类和接口完结面向目标编程
// 假如有一些通用办法,也能够用 Class + extends 办法
interface PostInterface {
 save(): void;
}
​
class MySQLProvider implements PostInterface {
 save() {
  // 将日志存入 MySQL 数据库
  console.log("hello");
  }
}
​
class PostService {
 constructor(
    @Inject(MySQLProvider) provider
  ) {  }
 
 save() {
  // 
  }
}

这样,只需求将各个存储 Store 运用相同的 interface 进行完结,即可替换存储源从 MySQL 到 MongoDB、File System 等其他地方。

运用装修

装修器是一种运用简略语法来为类、办法或特点增加额定功能的办法。它们是一种增强类的行为而不修正其完结的办法。

例如,能够运用装修器为办法增加日志记录:

function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
 let originalMethod = descriptor.value;
 descriptor.value = function(…args: any[]) {
  console.log(Calling ${propertyKey} with args: ${JSON.stringify(args)});
  let result = originalMethod.apply(this, args);
  console.log(Called ${propertyKey}, result: ${result});
    return result;
  }
}
​
class Calculator {
 @logMethod
 add(x: number, y: number): number {
  return x + y;
  }
}

还能够运用装修器为类、办法或特点增加元数据,这些元数据能够在运转时运用。

function setApiPath(path: string) {
  return function (target: any) {
    target.prototype.apiPath = path;
  }
}
@setApiPath("/users")
class UserService {
  // …
}
console.log(new UserService().apiPath); // "/users"

代码质量

一只水桶能装多少水取决于它最短的那块木板。

能够进步代码质量的办法有许多。以下几个方面常来综合评定代码质量:

  • 测验覆盖率,经过最少的测验用例完整覆盖一切代码分支。一般要求整体覆盖率 95% 以上。Branch 覆盖率 100%(一切 if、switch 的分支均需求跑到)。
  • Benchmark:比方循环,在不需求赋值的状况下,能够用 arr.forEach 会比 arr.map 功能更高。但 for 语句一定是最高的
  • 防止递归调用、或许呈现死循环的场景
  • 衔接运用后要开释,断线重连机制及超时机制
  • 内存开释,防止内存溢出
  • Validation: 输入参数校验,输入可容错规模宽
  • Transform: 输出参数格式化,回来类型明晰界说
  • 依靠注入:防止循环引用

在 js/ts 中,常用如下几种办法进步代码编写的质量。

测验驱动开发

能够参阅我曾经的几篇文章:

  • TDD、BDD: leader.js.cool/basic/node/…
  • BDD——行为驱动开发实践: leader.js.cool/experience/…
  • BDD——像盖房子相同写代码: leader.js.cool/experience/…

一直敞开严厉形式

在 TypeScript 中,严厉形式能够提供更严厉的类型查看和过错检测,协助开发者在开发过程中发现潜在的过错和类型问题。

// 在 tsconfig.json 中敞开严厉形式
{
  "compilerOptions": {
    "strict": true
  }
}

在敞开严厉形式时,需求留意一些言语特性的改变和标准,比方不能隐式地将 nullundefined 赋值给非空类型,不能在类界说之外运用 privateprotected 等等。

运用枚举类型界说常量

在 TypeScript 中,运用枚举类型能够便利地界说常量和枚举值,进步代码的可读性和可保护性。

// 运用枚举类型界说常量
enum MyEnum {
  Foo = "foo",
  Bar = "bar",
  Baz = "baz",
}
function doSomething(value: MyEnum) {
  console.log(value);
}
doSomething(MyEnum.Foo);

在运用枚举类型时,需求留意枚举值的正确性和可读性,防止呈现歧义或抵触。

运用 unknown 类型

有时,咱们或许没有有关变量类型的一切信息,但仍然需求在代码中运用它。在这种状况下,咱们能够利用 any 类型。可是,像任何强大的东西相同,运用 any 应该慎重和有目的地运用。而 unknown 类型是 TypeScript 3.0 中引入的一种强大且限制性更强的类型。它比 any 类型更具限制性,并能够协助你防止意外的类型过错。

any 不同的是,当你运用 unknown 类型时,除非你首先查看其类型,否则 TypeScript 不答应你对值履行任何操作。这能够协助你在编译时捕捉到类型过错,而不是在运转时。

例如,你能够运用 unknown 类型创建一个愈加类型安全的函数:

function printValue(value: unknown) {
  if (typeof value === "string") {
    console.log(value);
  } else {
    console.log("Not a string");
  }
}

“只读”和“只读数组”

当在 TypeScript 中处理数据时,你或许希望保证某些值无法更改。这便是“只读”和“只读数组”的用武之地。

“只读”关键字用于使目标的特点只读,意味着在创建后它们无法被修正。例如,在处理配置或常量值时,这十分有用。

interface Point {
  x: number;
  y: number;
}
let point: Readonly<Point> = {x: 0, y: 0};
point.x = 1; // TypeScript会报错,由于“point.x”是只读的

“只读数组”与“只读”相似,可是用于数组。它使一个数组变成只读状态,在创建后不能被修正。

let numbers: ReadonlyArray<number> = [1, 2, 3];
numbers.push(4); // TypeScript会报错,由于“numbers”是只读的

代码功率

包括两个部分,代码运转功率,和代码开发功率。这两者没什么实践相关,但却又避不开问题一起呈现。

运转功率

Javascript

参阅:github.com/alsotang/fa…

  • 传统 for 循环比 forEach 功能高出 15 倍
  • forEach 循环比 map 功能高出 1 倍
  • loadash 在大部分状况下比原生功能差许多许多
  • 新建数据数组用 new Array(arr.length) 功能最高

常见的一些特性都会有现成的功能测验用例。假如不确定的状况下,能够写两个办法对比一下,自己跑一个 Benchmark 脚本。

MySQL

参阅: github.com/js-benchmar…

  • 时刻类型:运用 timestamp (uint(10)) 比 DateTime 类型读写功能均会高
  • 字符串类型: 运用 char 会比 varchar 类型读写功能均会高
  • 长字符串: textblob 读写速度挨近, blob 略高不多可疏忽,但 blob 存储更推荐

小结

这部分没有什么实践的参阅经历,由于往往需求比较,不确定的两种计划,不一定会有现成的功能测验成果。运转功率需求经过跑 Benchmark 来确定。

开发功率

无为而治,是开发的最高境界。

无为不是无所作为,不是无所事事,而是不做无效的作业。

道家的榜首原则是“道法天然”。适应天然,不要过于故意,“去甚,去奢,去泰”。人要以天然的情绪对待天然,对待别人,对待自我。所以会有“天然——豁然——当然——怡然”。

合适的脚手架及东西

温馨主张 1:防止 GUI 依靠,图形界面的戳戳点点是十分低效的。比方画流程图,你的手速或许跟不上你的思维,那么这时分需求两点:

  • 进步打字速度:工欲善其事必先利其器,首先是物理外挂——一个好的键盘,比方 HHKB Type-S 等静电容键盘(我个人用的是无刻字的版本)、银轴或许光轴的机械键盘(实践证明其他轴体很难达到高速、精确地敲代码),然后便是操练打字速度,拒绝一阳指低头看键盘等坏习气。我个人的成果一般在每分钟敲击键盘 600 次。
  • 运用文字化图形东西,比方 Mermaid,经过 Markdown 代码块来生成图。

以此为例,在挑选脚手架东西的时分,以下几点主张:

  • 开发脚手架 CLI 支持 watch 、hot-reload。各类东西需求防止杂乱的配置
  • 削减自己保护脚本及重复造轮子。时刻、精力专心于事务自身
  • 防止运用长时刻不保护、用户运用量较少的第三方库及东西。合适很重要,但盛行、稳定更重要

温馨主张 2:防止东西依靠,比方:

  • IDE 中集成了 Git 就不会操作命令
  • 有了代码美化插件和代码标准插件,就记不住语法、函数规则、缩进换行等细节
  • 依靠智能提示和代码生成器,以及仿制、张贴大法

主动化生成

仍然以上面 class Time为例,我没有额定去界说类型声明,但会主动生成如下:

// out.d.ts
/**
 * 时刻
 */
declare class Time {
    #private;
    constructor(a?: moment.MomentInput);
    format(pattern?: string): string;
    toString(): string;
    get value(): number;
}
export { Time };

同理,能够主动完结的作业不要人为参与。类型的界说声明、Open API 的文档 Schema、GraphQL 的 Schema 等,都是。一起,也能够凭借东西进行主动化流程。比方主动化测验、CI/CD 等,进一步充分进步人效。

小结

勤能补拙。最好的东西是机械记忆。写代码这东西,想得心应手,仍是得多写。哪怕便是一句很简略的 console.log,有的人打这么多个字符,也比用一个用插件 C+L+空格 敲三个键更飘逸更流通。

提高开发功率,运转功率,以及下降代码过错率这些许多问题,不能够去依靠东西和结构。

代码阅览性

在不凭借第三方东西,比方直接在 Github 上进行 Code Review 时,能够明晰看懂代码的逻辑。常见的问题有:

  • 代码过度封装,满是各种函数的嵌套,并且即使命名容易了解,不确定内部是否有问题。在调试过程中要不断打断点跳入的,会糟蹋许多时刻在排查和调试上
  • 正则表达式的运用:代码人都应该会写正则表达式,至少能看懂。所以正则表达式的可阅览性暂时不去具体评论
  • 一些骚套路完结,代码不确定 Side-Effect 的
  • 一些摒弃的办法办法,显得代码臃肿冗长
  • 没有 运用更具可读性的办法界说类型 (以及后来常见的 infer 推导等)
  • 代码不行整洁(能够从 Typescript 代码整洁之道 中参阅学习部分有用信息)

大部分状况下仁者见仁智者见智,当不太懂的小白也能大约看懂代码的意思时,阅览性就算能够了。相似于白居易写诗先问老妪是否听懂。

而有的时分,你会发现,在读一段代码的时分,会有这样一种感慨——“小学生的心思像星空,我看得见却完全看不懂。”这种状况,就十分为难了。写代码寻求的不是逼逼仄仄的东西拿来炫技,而是在满意需求的前提下,高效,易于团队的协同。

其他

下期分化。