发布于 2025-12-08 0 阅读
0

不健康的代码:原始代码过度使用 原始代码过度使用的识别 欺骗性布尔值 如何保持代码健康 保持联系 导航您的软件开发职业通讯

不健康的代码:过度使用原始代码

原始过度使用的识别

欺骗性布尔值

如何保持代码健康

保持联系

导航你的软件开发职业通讯

经典的“代码异味”之一被称为原始过度使用。

它看似简单。


注意:这是我的书《重构 TypeScript:保持代码健康》的摘录。


重构 TypeScript 书籍


原始过度使用的识别

以此代码为例:

const email: string = user.email;

if(email !== null && email !== "") {
    // Do something with the email.
}
Enter fullscreen mode Exit fullscreen mode

注意到我们正在处理电子邮件的原始数据吗?

或者,考虑一下:

const firstname = user.firstname || "";
const lastname = user.lastname || "";
const fullName: string = firstname + " " + lastname;
Enter fullscreen mode Exit fullscreen mode

注意到所有额外的检查,以确保用户名不被篡改了吗null?你肯定见过这样的代码。

这里出了什么问题?

这段代码有什么问题?有几点需要思考:

  • 这种逻辑是不可共享的,因此会在各处被复制

  • 在更复杂的场景中,很难看出底层业务概念代表什么(这导致代码难以理解)

  • 如果存在一个潜在的商业概念,那么它是隐含的,而不是明确的

偶然的商业理念

上述代码示例中的业务概念类似于用户的显示名称全名

然而,这个概念只是暂时存在于一个恰好被正确命名的变量中。它会在其他地方被同样命名吗?如果你的团队里还有其他开发人员——很可能不会

我们的代码从业务角度来看可能难以掌握,在复杂场景中难以理解,并且无法与应用程序中的其他地方共享。

我们该如何处理这个问题?

欺骗性布尔值

原始类型应该是我们在代码中创建更有用的面向业务的概念/抽象的构建块。

这有助于每个特定的业务概念将其所有逻辑集中在一个地方(这意味着我们可以更轻松地共享和推理它),实现更强大的错误处理,减少错误等。

我想看看我经历过的导致原始过度使用的最常见原因。我经常看到这种情况。

设想

假设我们正在开发一个网络应用程序,帮助客户在线销售二手物品。

我们被要求在系统中验证用户身份的部分添加一些额外的规则。

目前,系统仅检查用户是否已成功验证。

const isAuthenticated: boolean = await userIsAuthenticated(username, password);

if(isAuthenticated) {
    redirectToUserDashboard();
} else {
    returnErrorOnLoginPage("Credentials are not valid.");
}
Enter fullscreen mode Exit fullscreen mode

新商业规则

我们公司现在要求我们检查用户是否处于活跃状态。不活跃的用户将无法登录。

许多开发人员会做这样的事情:

const user: User = await userIsAuthenticated(username, password);
const isAuthenticated: boolean = user !== null;

if(isAuthenticated) {
    if(user.isActive) {
        redirectToUserDashboard();
    } else {
        returnErrorOnLoginPage("User is not active.");
    }
} else {
    returnErrorOnLoginPage("Credentials are not valid.");
}
Enter fullscreen mode Exit fullscreen mode

哦不!我们引入了代码异味,我们知道这会导致可维护性问题!

我们现在有一些空检查和嵌套条件(它们都是不健康代码的标志,在《重构 TypeScript》一书中有所提及。)

因此,让我们首先通过应用(a)特殊情况模式和(b)保护子句来重构它(这两种技术在书中都有详细解释。)

// This will now always return a User, but it may be a special case type
// of User that will return false for "user.isAuthenticated()", etc.
const user: User = await userIsAuthenticated(username, password);

// We've created guard clauses here.
if(!user.isAuthenticated()) {
    returnErrorOnLoginPage("Credentials are not valid.");
}

if(!user.isActive()) {
    returnErrorOnLoginPage("User is not active.");
}

redirectToUserDashboard();
Enter fullscreen mode Exit fullscreen mode

好多了。

更多规则...

现在您的经理已经看到您能够多快地添加新的业务规则,他们还需要一些其他规则。

  1. 如果用户的会话已经存在,则将用户发送到特殊的主页。

  2. 如果用户由于登录尝试次数过多而锁定了他们的帐户,则将他们发送到一个特殊页面。

  3. 如果这是用户首次登录,则将他们发送到特殊的欢迎页面。

哎呀!

如果您已经在这个行业工作了几年,那么您就会知道这种情况有多么普遍!

乍一看,我们可能会做一些天真的事情:

// This will now always return a User, but it may be a special case type
// of User that will return false for "user.isAuthenticated()", etc.
const user: User = await userIsAuthenticated(username, password);

// We've created guard clauses here.
if(!user.isAuthenticated()) {
    returnErrorOnLoginPage("Credentials are not valid.");
}

if(!user.isActive()) {
    returnErrorOnLoginPage("User is not active.");
}

if(user.alreadyHadSession()) {
    redirectToHomePage();
}

if(user.isLockedOut()) {
    redirectToUserLockedOutPage();
}

if(user.isFirstLogin()) {
    redirectToWelcomePage();
}

redirectToUserDashboard();
Enter fullscreen mode Exit fullscreen mode

注意到了吗,因为我们引入了保护子句,所以在这里添加新逻辑变得容易多了?这是提高代码质量的一大好处——它能让将来的代码修改和添加新逻辑变得更容易。

但是,在这种情况下,存在一个问题。你能发现吗?

我们的User课程正在成为所有身份验证逻辑的垃圾场。

真的那么糟糕吗?

有那么糟糕吗?是的。

想想看:你的应用中还有哪些地方需要这些数据?没有地方——都是身份验证逻辑。

一种重构方法是创建一个名为的新类,AuthenticatedUser并在该类中只放置与身份验证相关的逻辑。

这将遵循单一责任原则。

但是,针对这个特定场景,我们可以采取更简单的解决方法。

只使用枚举

每当我看到这种模式(方法的结果是一个布尔值,或者是一个具有布尔值的对象,这些布尔值会被立即检查/测试)时,用枚举替换布尔值是一种更好的做法。

从上面的最后一个代码片段中,让我们将方法更改userIsAuthenticated为更准确地描述我们正在尝试做的事情:tryAuthenticateUser

并且,我们不会返回 aboolean或 a User- 我们将返回一个枚举,告诉我们确切的结果是什么(因为这就是我们感兴趣的)。

enum AuthenticationResult {
    InvalidCredentials,
    UserIsNotActive,
    HasExistingSession,
    IsLockedOut,
    IsFirstLogin,
    Successful
}
Enter fullscreen mode Exit fullscreen mode

我们的新枚举将指定尝试验证用户的所有可能结果。

接下来,我们将使用该枚举:

const result: AuthenticationResult = await tryAuthenticateUser(username, password);

if(result === AuthenticationResult.InvalidCredentials) {
    returnErrorOnLoginPage("Credentials are not valid.");
}

if(result === AuthenticationResult.UserIsNotActive) {
    returnErrorOnLoginPage("User is not active.");
}

if(result === AuthenticationResult.HasExistingSession) {
    redirectToHomePage();
}

if(result === AuthenticationResult.IsLockedOut) {
    redirectToUserLockedOutPage();
}

if(result === AuthenticationResult.IsFirstLogin) {
    redirectToWelcomePage();
}

if(result === AuthenticationResult.Successful) {
    redirectToUserDashboard();
}
Enter fullscreen mode Exit fullscreen mode

注意到这样可读性提高了吗?而且,我们不再User用一堆不必要的额外数据来污染我们的类了!

我们返回一个值。这是简化代码的好方法。

这是我最喜欢的重构之一!希望你也会觉得它有用。

奖励:策略模式

每当我使用这种重构时,我就会自然而然地知道策略模式可能会对我们提供更多帮助。

想象一下上面的代码有更多的业务规则和路径。

我们可以使用策略模式的一种形式进一步简化它:

const strategies: any = [];

strategies[AuthenticationResult.InvalidCredentials] = 
    () => returnErrorOnLoginPage("Credentials are not valid.");
strategies[AuthenticationResult.UserIsNotActive] = 
    () => returnErrorOnLoginPage("User is not active.");
strategies[AuthenticationResult.HasExistingSession] = 
    () => redirectToHomePage();
strategies[AuthenticationResult.IsLockedOut] = 
    () => redirectToUserLockedOutPage();
strategies[AuthenticationResult.IsFirstLogin] = 
    () => redirectToWelcomePage();
strategies[AuthenticationResult.Successful] = 
    () => redirectToUserDashboard();

strategies[result]();
Enter fullscreen mode Exit fullscreen mode

如何保持代码健康

这篇文章摘自《重构 TypeScript》,它旨在成为一种平易近人且实用的工具,帮助开发人员更好地构建高质量的软件。

重构 TypeScript 书籍

保持联系

不要忘记通过以下方式与我联系:

导航你的软件开发职业通讯

一封电子邮件简报,助您提升软件开发职业水平!您是否想过:

✔ 软件开发人员通常经历哪些阶段?
✔ 我如何知道自己处于哪个阶段?如何进入下一个阶段?
✔ 什么是技术领导者?如何成为技术领导者?
✔ 有人愿意陪伴我并解答我的疑问吗?

听起来很有趣?加入社区吧!

文章来源:https://dev.to/jamesmh/unhealthy-code-primitive-overuse-7mh