从前,我以为:“好的代码像首诗,烂的代码像坨屎。”
但关于代码质量的好坏、阅览性的好坏,却没有一个有效的衡量标准。每个人心中的一杆秤,是不同的。
那么多年过去了,仍然无法去统一一切人的编程思想。而什么样的代码,才是好的代码,渐渐地,在心中有了一部分标准的答案。
在做一个项目时,经过以下几个方面因素来考虑代码架构的规划:
- 进步开发的人效,不管是单枪匹马的项目,仍是团队协作的。在造轮子的时分尤其需求留意,轮子造得好或许用得好,能够事半功倍
- 代码具有可扩展性和可移植性。由于许多实践的原因,大部分的需求是不明晰的技能选型是不固定的。所以开始的规划很关键,直接影响了后续的迭代保护成本
- 要有大局观。条条大路通罗马,异曲同工。同一种问题,或许有多种解决计划。学会变通,并在项目中灵敏运用。在做挑选的时分,权衡取舍,不能捡了芝麻,丢了西瓜。不需求在某一个点上捉住不放,而是需求德智体全面发展。
不管是在一个项目中,仍是在一个人的开发生涯中,慢慢地堆集沉积下来,应该从代码中体现出编写者的思想来,融入代码魂灵。而不是东一榔头,西一木棒,没有任何的特征;也不是某一方面深钻到了极致,像偏科严重,疏忽了其他的方面。
关于个人来说,终究应当变成“一精多专”,在某一个中心范畴规模称为领袖,在其他各个方面均有涉猎。关于项目来说,终究应当变成,输出中心技能价值,产品各方面挑不出太大缺点。
最佳实践
没有银弹。
一切的最佳实践或许都是阶段性的,或许针对某个范畴规模内的。不必故意魔化某几类技能、言语、东西、计划之类。
写代码从新手到入门,却有必要需求养成良好的习气。比方如下几点,是不管在任何言语、项目中,都能够推行的:
- 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';
}
这时分,就能够发现一些端倪,比方其他都用默认值,arg3
用 10
:
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(办法)
}
此刻,已经有三个办法名了 format
、addDay
和 subDay
,假如 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
}
}
在敞开严厉形式时,需求留意一些言语特性的改变和标准,比方不能隐式地将
null
或undefined
赋值给非空类型,不能在类界说之外运用private
和protected
等等。
运用枚举类型界说常量
在 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
类型读写功能均会高 - 长字符串:
text
与blob
读写速度挨近,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 代码整洁之道 中参阅学习部分有用信息)
大部分状况下仁者见仁智者见智,当不太懂的小白也能大约看懂代码的意思时,阅览性就算能够了。相似于白居易写诗先问老妪是否听懂。
而有的时分,你会发现,在读一段代码的时分,会有这样一种感慨——“小学生的心思像星空,我看得见却完全看不懂。”这种状况,就十分为难了。写代码寻求的不是逼逼仄仄的东西拿来炫技,而是在满意需求的前提下,高效,易于团队的协同。
其他
下期分化。