返回 引气・Java 气海初拓

09银行系统项目复盘:从学生作业到生产级别的三次架构演变

博主
大约 21 分钟

银行系统项目复盘:从学生作业到生产级别的三次架构演变

大一那年,我用300行Java代码写了个“银行系统”,得意地交作业。直到工作后,我亲眼看到因为并发问题,同一个账户被取走了两笔钱——那一刻我才明白,课堂项目和生产系统之间,隔着一个太平洋

开篇:那个让我差点挂科的“完美作业”

2024年,我的Java课程作业:

java

// 我的“杰作” - 账户类
public class Account {
    public String id;      // 卡号 - public,方便!
    public String name;    // 用户名 - public,方便!
    public double money;   // 余额 - public,方便!
    
    public void getMoney(double m) {
        money = money - m;  // 直接操作!
    }
}

// 测试代码
public static void main(String[] args) {
    Account a = new Account();
    a.id = "123456";
    a.name = "张三";
    a.money = 1000;
    
    a.getMoney(500);
    System.out.println("余额:" + a.money);  // 输出500
}

教授给的评语是: “封装性零分,线程安全性零分,安全性零分。建议重修。”

当时的我不服气:“能跑起来不就行了吗?”

第一次重构:从“能跑就行”到“面向对象”

1.1 封装性觉醒:我不再相信任何人

工作第一年,导师看了我的代码后说:“在银行系统里,public 修饰的余额字段,相当于把金库密码贴在门口。”

重构前(学生的思维):

java

// 问题1:余额可以被直接修改
account.money = 1000000;  // 黑客狂喜!

// 问题2:没有校验
account.getMoney(-1000);  // 取负1000?居然成功了!

// 问题3:并发灾难
// 线程A:检查余额(1000) > 取款金额(800) → 通过
// 线程B:检查余额(1000) > 取款金额(800) → 通过
// 线程A:执行取款,余额变为200
// 线程B:执行取款,余额变为-600(灾难!)

重构后(程序员的思维):

java

public class BankAccount {
    // 所有字段私有化 - 金库上锁
    private final String accountId;  // final:卡号不可变
    private final String holderName; // final:用户名不可变
    private volatile double balance;  // volatile:保证可见性
    private final double dailyLimit;  // 日限额
    
    // 构造器私有,通过工厂方法创建 - 控制创建过程
    private BankAccount(String accountId, String holderName, double initialBalance, double dailyLimit) {
        validateId(accountId);
        validateName(holderName);
        validateAmount(initialBalance);
        
        this.accountId = accountId;
        this.holderName = holderName;
        this.balance = initialBalance;
        this.dailyLimit = dailyLimit;
    }
    
    // 工厂方法 - 统一入口
    public static BankAccount create(String holderName, double initialBalance) {
        String accountId = generateAccountId();
        double limit = calculateLimit(initialBalance);
        return new BankAccount(accountId, holderName, initialBalance, limit);
    }
    
    // 取款方法 - 加锁保证线程安全
    public synchronized WithdrawalResult withdraw(double amount) {
        // 校验1:金额必须为正
        if (amount <= 0) {
            return WithdrawalResult.failure("取款金额必须大于0");
        }
        
        // 校验2:不能超过余额
        if (amount > balance) {
            return WithdrawalResult.failure("余额不足");
        }
        
        // 校验3:不能超过日限额
        if (amount > dailyLimit) {
            return WithdrawalResult.failure("超过单日取款限额");
        }
        
        // 校验4:最小取款单位(ATM机限制)
        if (amount % 100 != 0) {
            return WithdrawalResult.failure("取款金额必须为100的整数倍");
        }
        
        // 执行取款
        balance -= amount;
        
        // 记录交易
        TransactionRecorder.recordWithdrawal(accountId, amount, balance);
        
        return WithdrawalResult.success(amount, balance);
    }
    
    // 存款方法
    public synchronized DepositResult deposit(double amount) {
        // 校验逻辑类似...
    }
    
    // 唯一公开的getter
    public double getBalance() {
        return balance;
    }
    
    // 账户信息DTO - 不暴露完整对象
    public AccountInfo getAccountInfo() {
        return new AccountInfo(
            accountId,
            holderName,
            balance,
            dailyLimit
        );
    }
}

这次重构的教训:

  1. 封装是信任的边界:不相信调用者,不相信同事,甚至不相信明天的自己
  2. 校验是安全的第一道防线:每个输入都可能是恶意的
  3. 并发不是可选项:银行系统天生就是并发的

1.2 业务逻辑的陷阱:转账不只是“A减B加”

image-20260201220941767

我犯过的经典错误:

java

// 错误示范:天真的转账
public void transfer(String fromId, String toId, double amount) {
    Account from = findAccount(fromId);
    Account to = findAccount(toId);
    
    from.withdraw(amount);  // 第一步:扣款
    to.deposit(amount);     // 第二步:存款
}

问题: 如果在第一步和第二步之间程序崩溃了,钱就消失了!

正确做法:事务性操作

java

public TransferResult transfer(String fromId, String toId, double amount) {
    // 校验:不能给自己转账
    if (fromId.equals(toId)) {
        return TransferResult.failure("不能给自己转账");
    }
    
    // 获取两个账户的锁(按固定顺序,避免死锁)
    Account from = lockAccount(fromId);
    Account to = lockAccount(toId);
    
    try {
        // 在一个事务中执行
        return executeInTransaction(() -> {
            // 扣款
            WithdrawalResult withdrawal = from.withdraw(amount);
            if (!withdrawal.isSuccess()) {
                return TransferResult.failure("扣款失败: " + withdrawal.getMessage());
            }
            
            // 存款
            DepositResult deposit = to.deposit(amount);
            if (!deposit.isSuccess()) {
                // 回滚:把钱加回去
                from.rollbackWithdrawal(amount);
                return TransferResult.failure("存款失败,已回滚");
            }
            
            // 记录转账
            TransactionRecorder.recordTransfer(fromId, toId, amount);
            
            return TransferResult.success(amount, from.getBalance());
        });
    } finally {
        // 释放锁
        unlockAccount(from);
        unlockAccount(to);
    }
}

第二次重构:从“单机玩具”到“分布式微服务”

2.1 单一职责原则:银行不是一个大类

image-20260201221030348

我见过最可怕的银行系统代码:

java

// God Class - 上帝类
public class BankSystem {
    // 管理账户
    private List<Account> accounts;
    
    // 管理用户
    private List<User> users;
    
    // 管理交易
    private List<Transaction> transactions;
    
    // 管理日志
    private List<Log> logs;
    
    // 连接数据库
    private Connection conn;
    
    // 发送邮件
    private EmailSender emailSender;
    
    // 生成报表
    private ReportGenerator reportGenerator;
    
    // 200多个方法...
    public void createAccount() { /* ... */ }
    public void deleteAccount() { /* ... */ }
    public void transferMoney() { /* ... */ }
    public void generateStatement() { /* ... */ }
    public void sendNotification() { /* ... */ }
    public void backupDatabase() { /* ... */ }
    // ... 还有195个方法
}

重构:按领域拆分

java

// 账户服务 - 只负责账户相关
public interface AccountService {
    Account createAccount(OpenAccountRequest request);
    Account getAccount(String accountId);
    void closeAccount(String accountId);
    void freezeAccount(String accountId);
    void unfreezeAccount(String accountId);
}

// 交易服务 - 只负责交易
public interface TransactionService {
    TransactionResult deposit(DepositRequest request);
    TransactionResult withdraw(WithdrawalRequest request);
    TransactionResult transfer(TransferRequest request);
    List<Transaction> getStatement(String accountId, DateRange range);
}

// 风控服务 - 只负责风险控制
public interface RiskControlService {
    RiskAssessment assessWithdrawal(WithdrawalRequest request);
    RiskAssessment assessTransfer(TransferRequest request);
    void flagSuspiciousActivity(String accountId, String reason);
}

// 通知服务 - 只负责通知
public interface NotificationService {
    void sendBalanceAlert(String accountId, BalanceAlert alert);
    void sendTransactionAlert(String accountId, TransactionAlert alert);
    void sendSecurityAlert(String accountId, SecurityAlert alert);
}

2.2 依赖注入:从“new”到“注入”

坏味道代码:

java

public class Bank {
    private AccountRepository repo = new AccountRepository();
    private EmailService email = new EmailService();
    private LogService log = new LogService();
    private AuditService audit = new AuditService();
    // ... 直接new了10个依赖
    
    public void transferMoney() {
        // 业务逻辑和具体实现紧耦合
        repo.updateBalance();
        email.sendNotification();
        log.record();
        audit.track();
    }
}

依赖注入重构:

java

public class BankTransferService {
    // 通过接口依赖,而不是具体实现
    private final AccountRepository accountRepo;
    private final NotificationService notificationService;
    private final TransactionLogger transactionLogger;
    private final AuditTrail auditTrail;
    private final RiskControlService riskControl;
    
    // 依赖通过构造器注入
    @Inject
    public BankTransferService(
        AccountRepository accountRepo,
        NotificationService notificationService,
        TransactionLogger transactionLogger,
        AuditTrail auditTrail,
        RiskControlService riskControl) {
        
        this.accountRepo = accountRepo;
        this.notificationService = notificationService;
        this.transactionLogger = transactionLogger;
        this.auditTrail = auditTrail;
        this.riskControl = riskControl;
    }
    
    public TransferResult transfer(TransferCommand command) {
        // 1. 风险控制检查
        RiskAssessment risk = riskControl.assessTransfer(command);
        if (risk.isHighRisk()) {
            return TransferResult.rejected("风控拒绝");
        }
        
        // 2. 执行转账(事务性)
        TransferResult result = accountRepo.transferInTransaction(command);
        
        // 3. 记录日志
        transactionLogger.logTransfer(command, result);
        
        // 4. 审计追踪
        auditTrail.recordTransfer(command);
        
        // 5. 发送通知
        if (result.isSuccess()) {
            notificationService.sendTransferNotification(command);
        }
        
        return result;
    }
}

依赖注入的好处:

  1. 可测试性:可以轻松Mock依赖
  2. 可替换性:更换实现只需改配置
  3. 单一职责:每个类只关注自己的核心逻辑

第三次重构:从“过程式思维”到“领域驱动设计”

3.1 贫血模型 vs 充血模型

image-20260201221117481

贫血模型(我早期的写法):

java

// 贫血的Account - 只有getter/setter
public class Account {
    private String id;
    private String name;
    private double balance;
    private AccountStatus status;
    
    // 只有数据,没有行为
    // getter/setter...
}

// 服务类承担所有业务逻辑
public class AccountService {
    public void withdraw(String accountId, double amount) {
        Account account = accountRepo.findById(accountId);
        
        // 所有业务逻辑都在服务层
        if (account.getBalance() < amount) {
            throw new InsufficientBalanceException();
        }
        
        if (account.getStatus() != AccountStatus.ACTIVE) {
            throw new AccountFrozenException();
        }
        
        account.setBalance(account.getBalance() - amount);
        accountRepo.save(account);
    }
}

问题: 业务逻辑分散在各个Service中,Account只是个数据容器。

充血模型重构(领域驱动设计):

java

// 充血的Account - 数据和行为在一起
public class Account {
    private AccountId id;
    private AccountHolder holder;
    private Money balance;
    private AccountStatus status;
    private List<Transaction> transactions;
    private DailyWithdrawalLimit dailyLimit;
    private LocalDate dailyWithdrawalDate;
    private Money dailyWithdrawnAmount;
    
    // 业务方法:取款
    public WithdrawalResult withdraw(Money amount, WithdrawalStrategy strategy) {
        // 校验1:账户状态
        if (!status.canWithdraw()) {
            return WithdrawalResult.rejected("账户状态不允许取款");
        }
        
        // 校验2:余额
        if (!balance.canAfford(amount)) {
            return WithdrawalResult.rejected("余额不足");
        }
        
        // 校验3:日限额
        if (!dailyLimit.canWithdraw(amount, dailyWithdrawalDate, dailyWithdrawnAmount)) {
            return WithdrawalResult.rejected("超过日取款限额");
        }
        
        // 校验4:取款策略
        ValidationResult validation = strategy.validate(this, amount);
        if (!validation.isValid()) {
            return WithdrawalResult.rejected(validation.getMessage());
        }
        
        // 执行取款
        Money newBalance = balance.subtract(amount);
        this.balance = newBalance;
        
        // 更新日取款记录
        dailyWithdrawnAmount = dailyWithdrawnAmount.add(amount);
        
        // 记录交易
        Transaction transaction = Transaction.withdrawal(this.id, amount, newBalance);
        transactions.add(transaction);
        
        // 领域事件
        DomainEventPublisher.publish(new MoneyWithdrawnEvent(this.id, amount, newBalance));
        
        return WithdrawalResult.success(amount, newBalance, transaction.getId());
    }
    
    // 业务方法:存款
    public DepositResult deposit(Money amount) {
        // 类似逻辑...
    }
    
    // 业务方法:转账
    public TransferResult transferTo(Account target, Money amount) {
        // 类似逻辑...
    }
    
    // 内部类:值对象
    public static class Money {
        private final BigDecimal amount;
        private final Currency currency;
        
        public Money(BigDecimal amount, Currency currency) {
            this.amount = amount.setScale(2, RoundingMode.HALF_EVEN);
            this.currency = currency;
        }
        
        public Money add(Money other) {
            validateSameCurrency(other);
            return new Money(this.amount.add(other.amount), currency);
        }
        
        public Money subtract(Money other) {
            validateSameCurrency(other);
            return new Money(this.amount.subtract(other.amount), currency);
        }
        
        public boolean canAfford(Money other) {
            validateSameCurrency(other);
            return this.amount.compareTo(other.amount) >= 0;
        }
        
        private void validateSameCurrency(Money other) {
            if (!this.currency.equals(other.currency)) {
                throw new CurrencyMismatchException();
            }
        }
    }
}

3.2 领域事件:系统解耦的关键

传统做法(紧耦合):

java

public class TransferService {
    private AccountRepository accountRepo;
    private EmailService emailService;
    private SmsService smsService;
    private AuditService auditService;
    private ReportService reportService;
    
    public void transfer(TransferRequest request) {
        // 1. 执行转账
        accountRepo.transfer(request);
        
        // 2. 发送邮件
        emailService.sendTransferEmail(request);
        
        // 3. 发送短信
        smsService.sendTransferSms(request);
        
        // 4. 审计
        auditService.recordTransfer(request);
        
        // 5. 生成报表
        reportService.generateTransferReport(request);
        
        // 问题:每新增一个后续操作,都要修改这个方法
    }
}

领域事件重构(事件驱动):

java

// 领域事件
public class MoneyTransferredEvent implements DomainEvent {
    private final String transactionId;
    private final String fromAccountId;
    private final String toAccountId;
    private final BigDecimal amount;
    private final Instant occurredAt;
    
    // getter...
}

// 转账服务(只负责核心业务)
public class TransferService {
    private AccountRepository accountRepo;
    private DomainEventPublisher eventPublisher;
    
    public TransferResult transfer(TransferRequest request) {
        // 1. 执行转账(事务内)
        TransferResult result = accountRepo.transferInTransaction(request);
        
        // 2. 发布领域事件
        if (result.isSuccess()) {
            MoneyTransferredEvent event = new MoneyTransferredEvent(
                result.getTransactionId(),
                request.getFromAccountId(),
                request.getToAccountId(),
                request.getAmount(),
                Instant.now()
            );
            
            eventPublisher.publish(event);
        }
        
        return result;
    }
}

// 事件处理器(各自独立)
@Component
class TransferEmailHandler implements DomainEventHandler<MoneyTransferredEvent> {
    private final EmailService emailService;
    
    @Override
    public void handle(MoneyTransferredEvent event) {
        emailService.sendTransferEmail(event);
    }
}

@Component
class TransferSmsHandler implements DomainEventHandler<MoneyTransferredEvent> {
    private final SmsService smsService;
    
    @Override
    public void handle(MoneyTransferredEvent event) {
        smsService.sendTransferSms(event);
    }
}

@Component  
class TransferAuditHandler implements DomainEventHandler<MoneyTransferredEvent> {
    private final AuditService auditService;
    
    @Override
    public void handle(MoneyTransferredEvent event) {
        auditService.recordTransfer(event);
    }
}

事件驱动的好处:

  1. 解耦:核心业务不知道也不关心后续操作
  2. 可扩展:新增处理器只需新增类,不改原有代码
  3. 可靠性:事件可以持久化,失败可重试
  4. 可观察性:所有操作都有事件追溯

实战:生产级银行系统的关键设计

image-20260201221216743

4.1 防并发问题:乐观锁 vs 悲观锁

场景: 两个ATM同时取款

方案1:悲观锁(数据库行锁)

java

public class PessimisticAccountRepository {
    public WithdrawalResult withdrawWithPessimisticLock(String accountId, BigDecimal amount) {
        // 开启事务
        return transactionTemplate.execute(status -> {
            // SELECT ... FOR UPDATE 锁定行
            Account account = jdbcTemplate.queryForObject(
                "SELECT * FROM accounts WHERE id = ? FOR UPDATE",
                new AccountRowMapper(),
                accountId
            );
            
            // 检查余额
            if (account.getBalance().compareTo(amount) < 0) {
                status.setRollbackOnly();
                return WithdrawalResult.failure("余额不足");
            }
            
            // 更新余额
            int rows = jdbcTemplate.update(
                "UPDATE accounts SET balance = balance - ? WHERE id = ?",
                amount, accountId
            );
            
            return WithdrawalResult.success(amount, account.getBalance().subtract(amount));
        });
    }
}

方案2:乐观锁(版本号)

java

public class OptimisticAccountRepository {
    public WithdrawalResult withdrawWithOptimisticLock(String accountId, BigDecimal amount) {
        int retries = 0;
        final int maxRetries = 3;
        
        while (retries < maxRetries) {
            // 1. 读取当前版本
            Account account = jdbcTemplate.queryForObject(
                "SELECT id, balance, version FROM accounts WHERE id = ?",
                new AccountRowMapper(),
                accountId
            );
            
            // 2. 业务校验
            if (account.getBalance().compareTo(amount) < 0) {
                return WithdrawalResult.failure("余额不足");
            }
            
            BigDecimal newBalance = account.getBalance().subtract(amount);
            
            // 3. 尝试更新(带版本检查)
            int updated = jdbcTemplate.update(
                "UPDATE accounts SET balance = ?, version = version + 1 " +
                "WHERE id = ? AND version = ?",
                newBalance, accountId, account.getVersion()
            );
            
            // 4. 更新成功?
            if (updated == 1) {
                return WithdrawalResult.success(amount, newBalance);
            }
            
            // 5. 版本冲突,重试
            retries++;
            try {
                Thread.sleep(50 * retries); // 指数退避
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
        
        return WithdrawalResult.failure("操作冲突,请重试");
    }
}

选择策略:

text

并发冲突频率?
├── 高 → 悲观锁(避免重试开销)
└── 低 → 乐观锁(更好的并发性能)

4.2 防重放攻击:交易幂等性

问题: 同一笔交易被重复提交

解决方案: 幂等性设计

java

public class IdempotentTransferService {
    private final IdempotencyStore idempotencyStore;
    
    public TransferResult transfer(TransferRequest request) {
        // 1. 生成幂等键(客户端提供或服务端生成)
        String idempotencyKey = request.getIdempotencyKey();
        if (idempotencyKey == null) {
            idempotencyKey = generateIdempotencyKey(request);
        }
        
        // 2. 检查是否已处理过
        Optional<TransferResult> cached = idempotencyStore.get(idempotencyKey);
        if (cached.isPresent()) {
            return cached.get(); // 返回缓存结果
        }
        
        // 3. 获取幂等锁
        IdempotencyLock lock = idempotencyStore.acquireLock(idempotencyKey);
        if (!lock.isAcquired()) {
            // 其他线程正在处理相同的请求
            throw new ConcurrentRequestException("请勿重复提交");
        }
        
        try {
            // 4. 执行转账
            TransferResult result = doTransfer(request);
            
            // 5. 保存结果到幂等存储
            idempotencyStore.save(idempotencyKey, result);
            
            return result;
        } finally {
            // 6. 释放锁
            lock.release();
        }
    }
    
    private String generateIdempotencyKey(TransferRequest request) {
        return String.format("%s-%s-%s-%s",
            request.getFromAccountId(),
            request.getToAccountId(),
            request.getAmount().toPlainString(),
            request.getTimestamp().toEpochMilli()
        );
    }
}

4.3 防数据不一致:最终一致性方案

分布式转账场景: 账户服务在A数据库,交易服务在B数据库

java

public class SagaTransferService {
    private final CommandBus commandBus;
    private final SagaStore sagaStore;
    
    public void transfer(TransferRequest request) {
        // 1. 创建Saga(长事务)
        String sagaId = sagaStore.createSaga("transfer", request);
        
        try {
            // 2. 第一步:扣款(可补偿)
            commandBus.send(new DebitCommand(
                request.getFromAccountId(),
                request.getAmount(),
                sagaId
            ));
            
            // 3. 第二步:存款(可补偿)
            commandBus.send(new CreditCommand(
                request.getToAccountId(),
                request.getAmount(),
                sagaId
            ));
            
            // 4. 第三步:记录交易(不可补偿,但可重试)
            commandBus.send(new RecordTransactionCommand(
                sagaId,
                request.getFromAccountId(),
                request.getToAccountId(),
                request.getAmount()
            ));
            
            // 5. 完成Saga
            sagaStore.completeSaga(sagaId);
            
        } catch (Exception e) {
            // 6. 失败:执行补偿
            sagaStore.failSaga(sagaId, e.getMessage());
            executeCompensation(sagaId);
            throw e;
        }
    }
    
    private void executeCompensation(String sagaId) {
        Saga saga = sagaStore.getSaga(sagaId);
        
        // 逆序执行补偿操作
        if (saga.isStepCompleted("recordTransaction")) {
            // 删除交易记录(幂等)
            commandBus.send(new DeleteTransactionCommand(sagaId));
        }
        
        if (saga.isStepCompleted("credit")) {
            // 回滚存款(补偿取款)
            commandBus.send(new CompensateCreditCommand(
                saga.getToAccountId(),
                saga.getAmount(),
                sagaId
            ));
        }
        
        if (saga.isStepCompleted("debit")) {
            // 回滚扣款(补偿存款)
            commandBus.send(new CompensateDebitCommand(
                saga.getFromAccountId(),
                saga.getAmount(),
                sagaId
            ));
        }
    }
}

从项目到产品:架构演进路线图

阶段1:单体应用(学生项目)

java

// 所有代码在一个项目里
BankSystem/
├── Account.java
├── Bank.java  
├── ATM.java
└── Main.java

阶段2:分层架构(初级工程师)

java

// 按技术职责分层
BankSystem/
├── controller/      # 控制层
├── service/         # 服务层  
├── repository/      # 数据层
├── model/           # 模型层
└── config/          # 配置层

阶段3:模块化架构(中级工程师)

java

// 按业务功能模块化
BankSystem/
├── account-module/      # 账户模块
├── transaction-module/  # 交易模块
├── customer-module/     # 客户模块
├── report-module/       # 报表模块
└── shared/             # 共享模块

阶段4:微服务架构(高级工程师)

java

// 每个服务独立部署
services/
├── account-service/     # 账户服务
├── transaction-service/ # 交易服务  
├── customer-service/    # 客户服务
├── notification-service/# 通知服务
├── audit-service/       # 审计服务
└── api-gateway/        # API网关

阶段5:事件驱动架构(架构师)

java

// 服务通过事件通信
services/
├── command-services/    # 命令服务(写操作)
├── query-services/      # 查询服务(读操作)
├── event-processors/    # 事件处理器
├── message-broker/      # 消息中间件
└── event-store/         # 事件存储

生产级银行系统的检查清单

安全性检查

  • 密码加密存储(bcrypt/scrypt)
  • HTTPS传输
  • SQL注入防护
  • XSS防护
  • CSRF令牌
  • 会话安全管理
  • 访问日志记录
  • 敏感数据脱敏

可靠性检查

  • 事务管理(ACID)
  • 幂等性设计
  • 重试机制
  • 熔断器模式
  • 降级策略
  • 超时设置
  • 资源隔离

性能检查

  • 数据库索引优化
  • 查询分页
  • 缓存策略(Redis)
  • 连接池配置
  • 异步处理
  • 批量操作
  • 读写分离

可维护性检查

  • 统一日志格式
  • 集中配置管理
  • API文档(Swagger)
  • 健康检查端点
  • 指标监控(Prometheus)
  • 分布式追踪
  • 代码覆盖率

写给Java初学者的真心话

不要被复杂的架构吓倒,记住:

  1. 所有复杂系统都是从简单开始的:先让程序跑起来,再考虑优化
  2. 理解原理比记住框架更重要:知道为什么,才知道怎么选
  3. 代码是写给人看的:可读性 > 聪明性
  4. 测试是信心的来源:没有测试的代码就像没有刹车的车
  5. 重构是常态:好代码是改出来的,不是一次写出来的

我的学习路线建议:

text

第一阶段:让程序跑起来
   ↓
第二阶段:让程序正确运行(加校验、异常处理)
   ↓
第三阶段:让程序安全运行(加锁、事务)
   ↓
第四阶段:让程序高效运行(优化、缓存)
   ↓
第五阶段:让程序可维护(设计模式、架构)

银行系统项目之所以经典,是因为它涵盖了软件开发的核心挑战:

  • 数据一致性(账户余额必须准确)
  • 安全性(资金不能被盗)
  • 可靠性(7×24小时可用)
  • 可审计性(每笔交易可追溯)

这些挑战,是所有严肃软件项目都会面对的。通过这个项目学到的,不只是Java语法,更是软件工程的思维方式

记住:今天你写的银行系统,明天可能真的会被使用。用对待生产系统的心态写每个项目,这是专业程序员和学生最大的区别。

知识点测试

读完文章了?来测试一下你对知识点的掌握程度吧!

评论区

使用 GitHub 账号登录后即可发表评论,支持 Markdown 格式。

如果评论系统无法加载,请确保:

  • 您的网络可以访问 GitHub
  • giscus GitHub App 已安装到仓库
  • 仓库已启用 Discussions 功能