文章

从 LeetCode 到工程实现:对简单题的思维差异

在毕业前的一年多时间里,我保持了每天刷 LeetCode 的习惯。 通过每日一题,我熟悉了各种类型的算法题,也积累了不少常见的解题套路。那段时间,算法部分在笔试面试中几乎没有成为我的短板🤗

不过入职之后,我逐渐停止了算法题的日常练习。更多精力放在公司项目的开发、后端技术栈的掌握,以及工程设计思维的积累上。 相比单纯的刷题,这部分的学习让我对系统设计、职责划分与代码抽象有了更深入的理解。

题目背景

今天心血来潮做了一道每日一题:

https://leetcode.cn/problems/simple-bank-system

题目要求设计一个 Bank 类,实现最基本的账户操作:

  • 转账:transfer(from, to, money)

  • 存款:deposit(account, money)

  • 取款:withdraw(account, money)

只要账户合法、余额充足,就返回 true;否则返回 false

看似简单,但它恰好是一道能体现算法实现与工程思维差异的好例子😎

题目实现(算法角度)

在算法题语境下,实现非常直接,只需要对账户合法性和余额进行校验即可:

class Bank {
    private final long[] balance;
​
    public Bank(long[] balance) {
        this.balance = balance;
    }
​
    public boolean transfer(int account1, int account2, long money) {
        if (account1 <= 0 || account2 <= 0 || account1 > balance.length || account2 > balance.length) {
            return false;
        }
        if (balance[account1 - 1] < money) {
            return false;
        }
        balance[account1 - 1] -= money;
        balance[account2 - 1] += money;
        return true;
    }
​
    public boolean deposit(int account, long money) {
        if (account <= 0 || account > balance.length) {
            return false;
        }
        balance[account - 1] += money;
        return true;
    }
​
    public boolean withdraw(int account, long money) {
        if (account <= 0 || account > balance.length) {
            return false;
        }
        if (balance[account - 1] < money) {
            return false;
        }
        balance[account - 1] -= money;
        return true;
    }
}

这段代码清晰、正确,也足以通过所有测试用例。 但从工程角度看,这样的实现还不够「优雅」——它只是对规则的直接翻译,而没有形成结构化的抽象。

工程视角的思考

在实际业务中,方法的语义往往需要更精确的抽象。 例如这里的 account - 1 虽然逻辑正确,但语义上不够清晰,最好通过一个专门的转换函数进行封装:

private int convertToAccountIndex(int account) {
    return account - 1;
}

接着我们会发现,账户合法性校验与索引转换其实是强相关的逻辑。 因此,更合理的做法是在同一个函数中处理验证与转换

private int convertToAccountIndex(int account) {
    if (account <= 0 || account > balance.length) {
        throw new IllegalArgumentException();
    }
    return account - 1;
}

此时问题出现了:题目要求返回 boolean,而我们抛出了异常。 那该如何在不破坏题目结构的前提下,优雅地表达异常状态呢?

抽取状态逻辑:用包装器实现职责分离

题目中每个方法返回的 boolean 值,其实并没有具体的业务语义。 它更像是一个状态标识:表示操作是否执行成功。

既然如此,我们可以将状态逻辑抽离出来,用统一的包装器来执行带异常的业务逻辑:

private static boolean execute(Runnable action) {
    try {
        action.run();
        return true;
    } catch (IllegalArgumentException e) {
        return false;
    }
}

这样,每个业务方法就只需关注核心逻辑,不再关心异常捕获或状态转换:

class Bank {
    private final long[] balance;
​
    public Bank(long[] balance) {
        this.balance = balance;
    }
​
    private int convertToAccountIndex(int account) {
        if (account <= 0 || account > balance.length) {
            throw new IllegalArgumentException();
        }
        return account - 1;
    }
​
    public boolean transfer(int account1, int account2, long money) {
        return execute(() -> {
            int a1 = convertToAccountIndex(account1);
            int a2 = convertToAccountIndex(account2);
            if (balance[a1] < money) throw new IllegalArgumentException();
            balance[a1] -= money;
            balance[a2] += money;
        });
    }
​
    public boolean deposit(int account, long money) {
        return execute(() -> {
            int idx = convertToAccountIndex(account);
            balance[idx] += money;
        });
    }
​
    public boolean withdraw(int account, long money) {
        return execute(() -> {
            int idx = convertToAccountIndex(account);
            if (balance[idx] < money) throw new IllegalArgumentException();
            balance[idx] -= money;
        });
    }
​
    private static boolean execute(Runnable action) {
        try {
            action.run();
            return true;
        } catch (IllegalArgumentException e) {
            return false;
        }
    }
}

这样的写法既满足了题目的要求,又让逻辑职责更加清晰, 工程上也具备了更好的可扩展性与一致性。

工程演化的方向

这种思路在真实项目中可以继续演化成更高层次的抽象:

抽象层次

特点

示例

包装器(Wrapper)

统一执行逻辑

统一异常捕获与日志

模板方法(Template Method)

抽象父类定义执行流程

子类实现具体操作

代理模式(Proxy)

将校验、鉴权等抽离

统一封装横切逻辑

切面(AOP)

系统级非侵入增强

日志、事务、监控等

从简单的 boolean 返回值,到最终在系统层统一控制逻辑, 这其实是一条完整的工程抽象演化路径🥰

小结

LeetCode 关注的是算法正确性,而工程实现关注的是结构与语义的清晰性。 当我们从「写对」转向「写好」,就意味着要更关注职责划分、异常传播、逻辑复用这些工程核心问题。

有趣的是,有时一道人畜无害的简单题, 也能成为重新审视编程方式的契机 —— 从“把问题解出来”,到“把逻辑设计好”, 这是从算法思维到工程思维的自然过渡🤗


License:  CC BY 4.0