代码是最好的注释

内容纲要

ECMAScript标准直接使用的问题

直接使用ECMAScript标准有很多问题,比如著名的 Null References: The Billion Dollar Mistakeconst chocolate: Product = new Product(null, null, null);,这种完全没有意义的代码以及需要返回空对象却返回了null等类似的问题太多,这里举个栗子:

正常情况下,代码是不需要注释的,代码的命名、执行的方法、通顺的逻辑就已经是最好的注释。如果开发者的代码需要大量的注释才能读懂,说明这种代码是不合格的,而ECMAScript开发人员很少有能做到 代码即注释 的能力或者其他原因(比如赶工、再比如赶工,绩效在哪放着呢🤣)没有做到,最终的结果是代码很难懂,比如:

if(a && b && c > 1 && d == 2 || !e || !f || h(g => g === 3)){
    //do something
}
//...
(a && b && c && d >= 0 || e <= 100) ? (f > 1 && g <= 10 ? a : b) : c

这种代码,超级点点点。。。

这种代码是很简洁,大眼看去是很高端大气上档次,但是。。。。

  1. 代码很难读
  2. 读了很难懂
  3. 懂了很难改
  4. 改了很难测试
  5. 测试发现问题花费太多时间修复

权限验证案例

拿后台管理系统经常做的权限来说,判断用户权限一般会这么写:

if(user.role === "admin" && user.isActive && user.permissions.some => p === "edit"){
    //do something
}

那么可以这么写:

const isAdmin: boolean = user.role === "admin"; 
const userIsActive: boolean = user.active; 
const userCanEdit: boolean = user.permissions.some(p => p === "edit");
const activeAdminCanEdit: boolean = isAdmin && userIsActive && userCanEdit;
if (activeAdminCanEdit) {
    //do something
}

但是这么写好像也有问题,因为直接限定了 admin ,如果只是个普通游客呢?

class User {
    isAdmin(): boolean {
    }
    isActive(): boolean {
    }
    canEdit(): boolean {
    }
    isActiveAdmin(): boolean {
    }
    isActiveAdminThatCanEdit(): boolean {
    }
}

如果业务复杂,也可以单独把每个方法拆出来:

class UserIsActiveAdmin {

    private _user: User;

    constructor(user: User) {
        this._user = user;
    }

    public invoke(): boolean {
        return this._user.isAdmin() && this._user.isActive();
    }
}

用的时候只需要:

const activeAdminCanEdit: boolean = new UserIsActiveAdmin(user).invoke() && user.canEdit();
if (activeAdminCanEdit) {
}

这个时候,发现似乎有什么东西是通用的🤗。

为了让逻辑更清晰,做下面的接口约定:

interface IPipeableCondition {
    check(): boolean;
}

然后,让其他的Class实现这个接口:

IPipeableCondition { 
    private _user: User;
    constructor(user: User) {
        this._user = user;
    } 
    public check(): boolean {
        return this._user.isAdmin() && this._user.isActive();
    }
}

其他的也实现这个接口:

const conditions: IPipeableCondition[] = [
    new UserIsActiveAdmin(user),
    new UserCanEdit(user),
    new UserIsNotBlacklisted(user),
    new UserLivesInAvailableLocation(user)
];
const valid = conditions.every(p => p.check());
if (valid) {
}

当然,虽然不推荐这么做,但至少比之前的好多了。

输入验证案例

在登录注册开发中,会经常开发手机号码验证、邮箱验证等功能,比如处理邮箱:

const email: string = user.email;
if (email !== null && email !== "") {
    //do something
}

比如当要电子邮件需要过滤垃圾类邮件域名时,当要区分内网外网时:

const domain: string = email.replace(/.*@/, "");
const userName = email.replace(domain, "");
const sendInternal: boolean = domain === "内网.外网";
const sendSpam: boolean = domain === "垃圾邮件.网址"; 
if (sendInternal) {
    if (userName === "info") {
        mailer.sendToCustomerServiceTeam(email, message);
    } else {
        mailer.mailToInternalServer(email, message);
    }
} else {
    throw "不能发送。";
}
if (sendSpam) {
}

以内外网邮件为例,稍微整理一下:

const domain: string = email.replace(/.*@/, "");
const userName = email.replace(domain, "");
const sendInternal: boolean = domain === "内网.外网";
if (sendExternal) {
    throw "不能发送。";
}
if (userName === "info") {
    mailer.sendToCustomerServiceTeam(email, message);
    return;
}
mailer.mailToInternalServer(email, message);

这里逻辑是有关联的,把邮件的地址单独独立出来处理:

class EmailAddress {
    private _value: string;
    private _domain: string;
    private _userName: string;
    constructor(value: string) {
        this._value = value;
        this._domain: string = value.replace(/.*@/, "");
        this._userName = value.replace(domain, "");
    }
    isExternal(): boolean {
        return this._domain !== "内网.外网";
    }
    isInfoUser(): boolean {
        return this._userName === "info";
    }
    value() {
        return this._value;
    }
}

身份验证案例

在注册用户中,会有大量长期不使用的账户,需要对账户限制,比如百度云将2019年年底前长期不登陆的百度云的账户云盘容量降至由2T降至100G;腾讯QQ、微信将长期不登陆的用户注销,直接删除所有账户数据;国外网站一般是锁定账户,解锁后才能正常使用等,对于一般需求来说只需要简单的验证:

const isAuthenticated: boolean = await userIsAuthenticated(username, password);
if (isAuthenticated) {
    redirectToUserDashboard();
} else {
    returnErrorOnLoginPage("验证无效");
}

如果现在需要长期不登陆的账户不能登录,需要先解除锁定,很多人喜欢这么写:

const user: User = await userIsAuthenticated(username, password);
const isAuthenticated: boolean = user !== null;
if (isAuthenticated) {
    if (user.isActive) {
        redirectToUserDashboard();
    } else {
        returnErrorOnLoginPage("用户长期没有登录");
    }
} else {
    returnErrorOnLoginPage("验证无效");
}

这么写当然是能实现业务的,但是建议这么写:

const user: User = await userIsAuthenticated(username, password);

if (!user.isAuthenticated()) {
    returnErrorOnLoginPage("验证无效");
}
if (!user.isActive()) {
    returnErrorOnLoginPage("用户长期没有登录");
}
redirectToUserDashboard();

但实际上,会有很多验证。由于,不清楚会有多少,需要做一个枚举:

enum AuthenticationResult {
    InvalidCredentials,
    UserIsNotActive,
    HasExistingSession,
    IsLockedOut,
    IsFirstLogin,
    Successful
}

这样就能确保做的验证是有意义的:

const result: AuthenticationResult = await tryAuthenticateUser(username, password);
if (result === AuthenticationResult.InvalidCredentials) {
    returnErrorOnLoginPage("验证无效");
}
if (result === AuthenticationResult.UserIsNotActive) {
    returnErrorOnLoginPage("用户长期没有登录");
}
if (result === AuthenticationResult.HasExistingSession) {
    redirectToHomePage();
}
if (result === AuthenticationResult.IsLockedOut) {
    redirectToUserLockedOutPage();
}
if (result === AuthenticationResult.IsFirstLogin) {
    redirectToWelcomePage();
}
redirectToUserDashboard();

再做一些简化:

const strategies: any = [];
strategies[AuthenticationResult.InvalidCredentials] = () => returnErrorOnLoginPage("验证无效");
strategies[AuthenticationResult.UserIsNotActive] = () => returnErrorOnLoginPage("用户长期没有登录");
strategies[AuthenticationResult.HasExistingSession] = () => redirectToHomePage();
strategies[AuthenticationResult.IsLockedOut] = () => redirectToUserLockedOutPage();
strategies[AuthenticationResult.IsFirstLogin] = () => redirectToWelcomePage();
strategies[AuthenticationResult.Successful] = () => redirectToUserDashboard();
strategies[result]();

但是,服务器返回的参数不可能只有这些,在调用方法的时候就会传递很多参数,可以这样:

private getUsers(includeInactive: boolean = false, filterText: string = null, orderByName: boolean = false, forHireDate: Date = null) : User[] {
} 
public getActiveUsers() : User[] {
    return getUsers(false);
} 
public getInactiveUsers() : User[] {
    return getUsers(true);
} 
public getActiveUsersByName(filter: string) : User[] {
    return getUsers(false, filter);
} 
public getActiveUsersOrderedAndFilteredByName(filter: string) : User[] {
    return getUsers(false, filter, true);
} 
public getActiveUsersForHireDate(hireDate: Date) : User[] {
    return getUsers(false, null, false, hireDate);
}

但是,随着项目的功能逐步增加,需要验证的也越来越多,于是,if-else 也越来越长,比如再加个订单的验证:

public async processOrder(orderId: string): Promise < ValidationMessage > {
    const user = await this.getUserFromSession();
    const userId = user.id;
    const userRole = user.role;
    const userAllowed: boolean = await this.userCanModifyOrder(userId, userRole);
    if(!userAllowed) {
        return new ValidationMessage("没有权限");
    }

let saveAttempts = 1;
    while(saveAttempts > 3) {
        const order = await this.getOrderById(orderId);
        if (order.isActive()) {
            const status: OrderStatus = order.getStatus();
            if (status == OrderStatus.Pending) {
            } else if (status == OrderStatus.Shipped) {
            } else if (status == OrderStatus.Cancelled) {
            } else if (status == OrderStatus.Returned) {
            }
        } else {
            const archiveOnDate: Date = await this.getOrderArchiveOnDate(orderId);
            if (order.getOrderedOnDate() > archiveOnDate) {
            }
        }
        const result: OrderUpdateResult = await this.tryUpdateOrder(order);
        if (result == OrderUpdateResult.Successful) {
            return new ValidationMessage("订单已更新");
        } else if (result == OrderUpdateResult.OrderVersionOutOfSync) {
            return new ValidationMessage("订单已过期,请重试");
        } else if (result == OrderUpdateResult.NetworkError) {
            saveAttempts++;
        }
    }
}

于是,代码变成了这样:

const status: OrderStatus = order.getStatus();
if (status == OrderStatus.Pending) {
    //do 了 something
} else if (status == OrderStatus.Shipped) {
    //do 了 something
} else if (status == OrderStatus.Cancelled) {
    //do 了 something
} else if (status == OrderStatus.Returned) {
    //do 了 something
}

这种情况,就用 策略模式 吧:

enum OrderStatus {
    Pending = 0,
    Shipped = 1,
    Cancelled = 2,
    Returned = 3
}

const strategies: Function[] = [];
strategies[OrderStatus.Pending] = this.processPendingOrder;
strategies[OrderStatus.Shipped] = this.processShippedOrder;
strategies[OrderStatus.Cancelled] = this.processCancelledOrder;
strategies[OrderStatus.Returned] = this.processReturnedOrder;
strategies[order.getStatus()]();

code enjoy!🤩🤩🤩

作者:indeex

链接:https://indeex.cc

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


发表评论

您的电子邮箱地址不会被公开。