如何写出高质量代码:六招助你轻松成功
招式一 YAGNI (You ain’t gonna need it)
只在需要时才添加代码和功能,避免不必要的复杂性和浪费。
两种类别的 YAGNI
- 产品需求设计做减法,每次只实现最必要的功能和特性。(考量点:开发周期、产品复杂度、开发成本)
- 软件架构设计做减法,减少依赖的组件或服务。(考量点:稳定性、成本、维护难度)
招式二 KISS (Keep it simple and stupid)
复杂的代码往往难以理解和维护,因此应该尽可能地保持代码简单。
不推荐: 这个函数混杂四个功能,
- 判断形状类型,矩形,圆形,三角形。
- 计算矩形面积的算法
- 计算圆形面积的算法
- 计算三角形面积的算法
function calculateArea(width: number, height: number, shape: string): number {
if (shape === 'rectangle') {
return width * height;
} else if (shape === 'circle') {
return Math.PI * Math.pow(width / 2, 2);
} else if (shape === 'triangle') {
return (width * height) / 2;
}
}
推荐做法:分拆为四个函数, 每个函数实现一个功能点,简单易维护。
function calculateRectangleArea(width: number, height: number): number {
return width * height;
}
function calculateCircleArea(radius: number): number {
return Math.PI * Math.pow(radius, 2);
}
function calculateTriangleArea(base: number, height: number): number {
return (base * height) / 2;
}
function calculateArea(shape: string, ...args: number[]): number {
switch (shape) {
case "rectangle":
return calculateRectangleArea(args[0], args[1]);
case "circle":
return calculateCircleArea(args[0]);
case "triangle":
return calculateTriangleArea(args[0], args[1]);
default:
throw new Error(`Unsupported shape: ${shape}`);
}
}
招式三 DRY (Don’t repeat yourself)
如果一段代码在多个地方使用,就应该把它提取出来,避免重复编写。
不同代码规模的DRY
- 函数,如果发现多个函数有重复的代码块,写个新函数吧 (当你严格遵循了KISS原则时,你的函数应该不会有重复的代码块)
- 类,如果发现多个类有重复的方法,写个新的基类吧
- 包,如果发现一个包在其他项目中也能复用,写个新的包吧
- 脚手架,使用脚手架提高项目开发效率和一致性
开启新项目时,为了避免重复的项目搭建流程,可以将该流程抽象成一个脚手架。这个脚手架可以包含项目初始化、依赖安装、配置文件生成等一系列步骤,并提供一些可配置的选项,以便根据不同项目的需求进行定制。使用脚手架可以显著提高项目的开发效率和一致性,并减少出错的机会。
招式四 六边形架构
应用程序分为三部分,核心业务逻辑、端口和外部适配器。
端口是我们的应用程序与外部世界进行通信的接口,例如数据库、消息队列、Web 服务等。
适配器是将端口与核心业务逻辑连接起来的组件,它实现了我们定义的端口,并将其注入到核心业务逻辑中。它们负责将外部数据转换为内部模型,并将内部模型转换为外部数据。
实践步骤
- 定义核心业务逻辑
- 定义端口和适配器
- 实现适配器
以用户注册业务逻辑举例:
不推荐:
function register(request: UserRegistrationRequest): void {
// 验证用户信息
if (request.username == null || request.username.trim() === "") {
throw new Error("Invalid username");
}
if (request.password == null || request.password.trim() === "") {
throw new Error("Invalid password");
}
if (request.email == null || request.email.trim() === "") {
throw new Error("Invalid email");
}
// 写入数据库
const user = new User(request.username, request.password, request.email);
saveUser(user);
// 发送通知
const notification = new Notification(request.email, "Welcome to our site!");
sendNotification(notification);
}
function saveUser(user: User): void {
// 写入数据库
}
function sendNotification(notification: Notification): void {
// 发送通知
}
推荐做法:采用六边形架构重构
思维步骤
- 我是客户,我要一个符合规范的注册服务,它接收符合规范的请求参数就能完成注册流程,我不管注册流程细节。
type Result = Promise<void>
// 服务规范
interface UserRegistrationPort {
register(request: UserRegistrationRequest): Result;
}
// 请求参数规范
interface UserRegistrationRequest {
username: string;
password: string;
email: string;
}
class UserRegistrationController {
constructor(private userRegistrationPort: UserRegistrationPort) {}
async register(request: UserRegistrationRequest): void {
const result = await this.userRegistrationPort.register(request);
}
}
- 我是服务供应商,我来管理这个服务的流程和规范,脏活累活外包出去。
- 流程:验证用户信息=>写入数据库=>发送通知
type Result = Promise<void>
// 用户信息验证服务的规范
interface IUserValidatorService {
validate(request: UserRegistrationRequest): Result;
}
// 写入数据库服务规范
interface IUserRepositoryService {
save(user: UserRegistrationRequest): Result;
}
// 通知规范
interface Notification {
to: string;
message: string;
}
// 发送通知服务规范
interface INotificationService {
send(notification: Notification): Result
}
class UserRegistrationAdapter implements UserRegistrationPort {
constructor(
private validator: IUserValidatorService,
private repository: IUserRepositoryService,
private notificationService: INotificationService
) {}
async register(request: UserRegistrationRequest): void {
try {
// 验证用户信息
await this.validator.validate(request)
// 写入数据库
await this.repository.save(request);
// 发送通知
const notification: Notification = {
to:request.email,
message: "Welcome to our site!"
}
await this.notificationService.send(notification);
}catch((e)=>{
throw new Error(e.message)
})
}
}
- 我是最底层的,专干别人不愿意干的活
// 用户信息验证
class UserValidator implements IUserValidatorService {
validate(request: UserRegistrationRequest): Promise<void> {
// 验证用户信息
return new Promise(async (resolve, reject) => {
if (
request.username != null &&
request.username.trim() !== "" &&
request.password != null &&
request.password.trim() !== "" &&
request.email != null &&
request.email.trim() !== ""
) {
resolve();
} else {
reject(new Error("invalid"));
}
});
}
}
// 写入数据库
class UserRepositoryService implements IUserRepositoryService{
save(user: UserRegistrationRequest): Promise<void>{
return new Promise(async (resolve,reject)=>{
// ...
resolve()
})
}
}
// 发送通知
class NotificationService implements INotificationService{
send(notification: Notification): Promise<void> {
return new Promise(async (resolve,reject)=>{
// ...
resolve()
})
}
}
招式五 SOLID
一组面向对象编程原则,旨在提高可维护性、可扩展性和可复用性
- S:单一职责原则(Single Responsibility Principle)
- O:开放封闭原则(Open-Closed Principle)
- L:里氏替换原则(Liskov Substitution Principle)
- I:接口隔离原则(Interface Segregation Principle)
- D:依赖倒置原则(Dependency Inversion Principle)
单一职责原则:一个类不要做太多事情,要专注,有点类似于 KISS 原则。
开放封闭原则:对扩展开放,对修改封闭。
以支付处理举例:
不推荐:每次新增支付方式就得修改PaymentProcessor 这个类,可能会导致其他支付方式也需要重新测试。
class PaymentProcessor {
processPayment(amount: number, paymentMethod: string) {
switch (paymentMethod) {
case 'AliPay':
console.log(`使用支付宝支付 $${amount}`);
// 实现支付宝支付
break;
case 'WeChatPay':
console.log(`使用微信支付 $${amount}`);
// 实现微信支付
break;
default:
throw new Error('未知的支付方式');
}
}
}
const paymentProcessor = new PaymentProcessor();
paymentProcessor.processPayment(100.00, 'AliPay');
推荐: 不会影响到已有的代码,仅需保证新增的代码正常即可。
interface PaymentStrategy {
pay(amount: number): void;
}
class AliPay implements PaymentStrategy {
pay(amount: number) {
console.log(`使用支付宝支付 $${amount}`);
// 实现支付宝支付
}
}
class WeChatPay implements PaymentStrategy {
pay(amount: number) {
console.log(`使用微信支付 $${amount}`);
// 实现微信支付
}
}
class PaymentProcessor {
private paymentStrategy: PaymentStrategy | undefined;
setPaymentStrategy(paymentStrategy: PaymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
processPayment(amount: number) {
if (!this.paymentStrategy) {
throw new Error('未选择支付方式');
}
this.paymentStrategy.pay(amount);
}
}
const paymentProcessor = new PaymentProcessor();
paymentProcessor.setPaymentStrategy(new AliPay());
paymentProcessor.processPayment(100.00);
里氏替换原则:使用继承时,子类必须能够替换掉父类并且不影响程序的正确性。我的理解是被继承的类应该是抽象类,或者干脆不用继承,用接口约束子类的行为。 一言以蔽之,别用继承,你 hold 不住它。
接口隔离原则:将一个庞大的接口拆分成多个小接口,以避免类依赖于它们不需要的方法。
依赖倒置原则:依赖于抽象而不是具体实现。类似于六边形架构的思路。
以支付处理举例:
不推荐:这种做法写死了只能用Paypal 网关,新增网关就要重写支付处理代码了。
class PaymentProcessor {
private paypalGateway: PaypalGateway;
constructor() {
this.paypalGateway = new PaypalGateway();
}
processPayment(amount: number) {
this.paypalGateway.sendPayment(amount);
}
}
class PaypalGateway {
sendPayment(amount: number) {
// Send payment using Paypal API
}
}
推荐:支付处理器不再依赖于具体点处理实现而是抽象接口,这对于替换实现很方便。
// PaymentGateway 接口定义了一些通用的支付操作
interface PaymentGateway {
pay(amount: number): void;
refund(amount: number): void;
}
// PayPalPaymentGateway 实现了 PaymentGateway 接口
class PayPalPaymentGateway implements PaymentGateway {
pay(amount: number) {
// PayPal 支付逻辑
}
refund(amount: number) {
// PayPal 退款逻辑
}
}
// StripePaymentGateway 实现了 PaymentGateway 接口
class StripePaymentGateway implements PaymentGateway {
pay(amount: number) {
// Stripe 支付逻辑
}
refund(amount: number) {
// Stripe 退款逻辑
}
}
// PaymentProcessor 高层模块依赖于 PaymentGateway 接口
class PaymentProcessor {
constructor(private paymentGateway: PaymentGateway) {}
processPayment(amount: number) {
this.paymentGateway.pay(amount);
}
processRefund(amount: number) {
this.paymentGateway.refund(amount);
}
}
// 使用 PayPalPaymentGateway 实例化 PaymentProcessor
const paymentProcessor = new PaymentProcessor(new PayPalPaymentGateway());
// 处理支付请求
paymentProcessor.processPayment(100);
// 处理退款请求
paymentProcessor.processRefund(50);
依赖倒置原则看起来有点像开放封闭原则,但两者侧重点不同,前者重在对依赖的管理,依赖接口而不是实现。后者重在不要修改已有的代码,想要改变软件行为引入一个新的符合规范的类。
招式六 IoC(Inversion of Control)
核心思想是将控制权从程序员手中转移给某个框架或容器,由框架或容器来管理对象之间的依赖关系和对象的生命周期。把分散在各处的对象 new 的操作集中在一起,并提供统一的访问入口。
不推荐:这是创建和使用对象的常规用法,不适用于大量对象的管理。
// 定义一个服务接口
interface IService {
doSomething(): void;
}
// 实现服务接口的具体类
class Service implements IService {
doSomething() {
console.log('Service is doing something.');
}
}
// 手动创建服务实例
const service = new Service();
// 使用服务实例
service.doSomething();
推荐用法:需要什么服务就跟容器要。
// 定义一个服务接口
interface IService {
doSomething(): void;
}
// 实现服务接口的具体类
class Service implements IService {
doSomething() {
console.log('Service is doing something.');
}
}
// 定义一个控制反转容器
class Container {
private services: { [key: string]: any } = {};
// 注册服务
register(key: string, service: any) {
this.services[key] = service;
}
// 获取服务实例
resolve<T>(key: string): T {
const service = this.services[key];
if (!service) {
throw new Error(`Service ${key} not found.`);
}
return service as T;
}
}
// 在容器中注册服务
const container = new Container();
container.register('service', new Service());
// 获取服务实例并使用
const service = container.resolve<IService>('service');
service.doSomething();
其他提升代码质量的手段和习惯
- 使用 ESLint 和 Prettier 进行代码格式化和规范检查
- 避免 any 类型,多用接口和泛型
- 总是声明函数的参数和返回值类型