程序员10年成长记:第9篇:从“搬砖”到“砌墙”——如何写出“可扩展”的代码

程序员10年成长记:第9篇:从“搬砖”到“砌墙”——如何写出“可扩展”的代码

引言

时间来到了2019年的春节,整个中国都沉浸在一种复杂的情绪中。一方面,中美贸易摩擦的阴云让经济前景变得扑朔迷离;另一方面,一部名为《流浪地球》的科幻电影点燃了所有人的激情,它讲述了一个带着地球去“流浪”的宏大故事。

“带着地球去流浪”,这几个字深深触动了启明科技的CEO。

公司的“凤凰项目”(单体电商)在2018年的“新零售”战役中,已经显现出“尾大不掉”的疲态。而拼多多的异军突起(2018年7月上市),更是让CEO焦虑万分,他意识到“社交+电商”的巨大威力。

在一次高层战略会上,CEO拍板决定,效仿“流浪地球”,启动一个代号为**“银河计划”(Galaxy Project)**的宏大工程:抛弃陈旧的“凤凰”单体架构,全面转向“云原生”微服务

这个计划的核心,是在上海新成立一个“研发中心”,轻装上阵,用全新的技术栈(Spring Cloud, Docker, Kubernetes, Redis, MQ)从零开始重构公司的核心业务。

小葵,凭借在“幽灵库存”事件和“复盘”中的出色表现,被老李力荐,成为了“银河计划”的先遣队成员。她告别了北京熟悉的老团队,独自一人来到上海,她的职级也从“工程师”晋升为“资深工程师”。

而张三,则选择留在了北京,继续维护“凤凰”单体项目。他觉得:“上海房价那么贵,K8s那么复杂,都是虚的,把Bug改了才是真的。”

小葵和张三的职业轨迹,在这一刻,正式拉开了第一个显著的岔口。

小故事:“银河”的引力与“凤凰”的“补丁”

  1. 新的“战场”,新的“需求”

小葵在上海入职的第一天,领到了一台全新的MacBook Pro和一张“银河计划”的架构图。她的第一个任务,就是构建“银河系”的“恒星”—— **“用户中心”(user-service)**微服务。

几乎在同一时间,CEO在产品评审会上,受“拼多多”刺激,提出了一个紧急需求:

“我们必须立刻支持社交登录!给两个月时间,App要上线**‘微信登录’和‘拼团匿名登录’**(一种临时的访客账户)!”

这个需求,同时发给了北京的张三和上海的小葵。

  • 北京(旧战场): 张三的任务是,在“凤凰”单体项目上,打个补丁,实现功能。

  • 上海(新战场): 小葵的任务是,在“用户中心”微服务上,构建一个可扩展的登录体系

  1. 张三的“If-Else”补丁

    张三打开了“凤凰”项目中那数万行的UserService.java,找到了一个叫login()的方法。他深吸一口气,他最擅长的就是“修补”。

    他决定在UserController里新建一个loginV2()接口,然后开始了他的“艺术创作”:

    // 张三的代码 (位于 UserService.java)
    public UserInfo loginV2(LoginRequest request) {
        String type = request.getLoginType();
        
        if ("PASSWORD".equals(type)) {
            // 1. 检查用户名密码
            // 2. 校验验证码
            // 3. 登录成功,返回用户信息
            // ... (省略50行代码) ...
            return userInfo;
    
        } else if ("WECHAT".equals(type)) {
            // 1. 调用微信API,用code换取openId
            String openId = wechatApiService.getOpenId(request.getCode());
            // 2. 检查openId是否已绑定
            User user = userDao.findByOpenId(openId);
            if (user != null) {
                // 3. 已绑定,直接登录
                return buildToken(user);
            } else {
                // 4. 未绑定,检查手机号是否存在
                User phoneUser = userDao.findByPhone(request.getPhone());
                if (phoneUser != null) {
                    // 5. 手机号存在,绑定微信
                    phoneUser.setOpenId(openId);
                    userDao.update(phoneUser);
                    return buildToken(phoneUser);
                } else {
                    // 6. 手机号不存在,创建新用户
                    User newUser = new User();
                    newUser.setPhone(request.getPhone());
                    newUser.setOpenId(openId);
                    // ... (省略20行) ...
                    userDao.insert(newUser);
                    return buildToken(newUser);
                }
            }
        } else if ("GUEST".equals(type)) {
            // 1. 创建一个匿名访客用户
            // 2. 设置一个较短的Token有效期
            // ... (省略30行代码) ...
            return guestInfo;
        }
        
        return null; // 理论上不会到这里
    }
    

    张三花了两天时间,写了近200行代码,终于调通了。他长舒一口气:“搞定,功能实现了。”

  2. 小葵的“可扩展”砌墙

    小葵在上海的白板前,思考的不是“如何实现微信登录”,而是:“未来一定还会有QQ登录、抖音登录、微博登录……我如何设计一个系统,让‘增加一种新登录方式’变得像‘插拔U盘’一样简单?

    这,就是“资深工程师”和“工程师”的思维差异。

    她想起了《Head First 设计模式》中的“策略模式”。她决定用“开闭原则”来武装自己的微服务。

    第一步:定义一个“策略”接口 (LoginStrategy)

    // 登录策略接口
    public interface LoginStrategy {
        
        /**
         * 登录处理
         * @param request 登录请求
         * @return 用户信息
         */
        UserInfo login(LoginRequest request);
        
        /**
         * 返回该策略支持的登录类型
         * @return 登录类型标识,如 "PASSWORD", "WECHAT"
         */
        String getStrategyName();
    }
    

    第二步:创建具体的“策略”实现 (Implementations)

    她利用Spring Boot的特性,将每个策略实现为一个@Component

    @Component
    public class PasswordLoginStrategy implements LoginStrategy {
        @Override
        public UserInfo login(LoginRequest request) {
            // 1. 检查用户名密码
            // ... (省略50行代码) ...
            return userInfo;
        }
    
        @Override
        public String getStrategyName() {
            return "PASSWORD";
        }
    }
    
    @Component
    public class WechatLoginStrategy implements LoginStrategy {
        @Autowired
        private WechatApiService wechatApiService;
        @Autowired
        private UserDao userDao;
        
        @Override
        public UserInfo login(LoginRequest request) {
            // 1. 调用微信API,用code换取openId
            // ... (这里是张三写的 100 行嵌套if-else逻辑) ...
            // ... 重点是,所有微信登录的复杂性,都被封装在了这里 ...
            return userInfo;
        }
    
        @Override
        public String getStrategyName() {
            return "WECHAT";
        }
    }
    
    @Component
    public class GuestLoginStrategy implements LoginStrategy {
        // ... 访客登录的逻辑 ...
        @Override
        public String getStrategyName() { return "GUEST"; }
        @Override
        public UserInfo login(LoginRequest request) { /* ... */ }
    }
    

    第三步:创建“策略工厂” (LoginStrategyFactory)

    小葵利用Spring的依赖注入,巧妙地构建了一个“工厂”,它在启动时会自动“收集”所有LoginStrategy的实现。

    @Component
    public class LoginStrategyFactory implements InitializingBean { // Spring的Bean初始化接口
        
        @Autowired
        private List<LoginStrategy> strategies; // Spring会注入所有LoginStrategy的实现
    
        private Map<String, LoginStrategy> strategyMap;
    
        /**
         * 在Spring Bean初始化后,自动构建一个Map
         */
        @Override
        public void afterPropertiesSet() throws Exception {
            strategyMap = strategies.stream()
                    .collect(Collectors.toMap(LoginStrategy::getStrategyName, s -> s));
        }
    
        /**
         * 根据类型获取对应的策略
         */
        public LoginStrategy getStrategy(String type) {
            LoginStrategy strategy = strategyMap.get(type);
            if (strategy == null) {
                throw new UnsupportedOperationException("不支持的登录类型: " + type);
            }
            return strategy;
        }
    }
    

    第四步:重构“用户服务” (UserService)

    现在,小葵的UserService变得异常简洁和稳定:

    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        private LoginStrategyFactory factory;
    
        @Override
        public UserInfo login(LoginRequest request) {
            // 1. 从工厂获取对应的策略
            LoginStrategy strategy = factory.getStrategy(request.getLoginType());
            
            // 2. 执行策略
            // 核心:这里不关心到底是哪种登录,只管调用接口
            return strategy.login(request); 
        }
    }
    
  3. “Kicker”——新需求的“拷问”

一个月后,产品总监(小红)在“银河计划”的周会上,果然提出了新需求:“‘抖音’最近太火了,我们要立刻上**‘抖音登录’**!下周就要!”

  • 北京(张三):

  • 张三的脸都绿了。他战战兢兢地打开那个200行的loginV2方法,在GUEST的else if下面,又加了一个else if (“DOUYIN”.equals(type)) { … }。

  • 在添加新逻辑时,他不小心碰到了WECHAT分支里一个变量,导致微信登录在测试环境直接NPE(空指针)。

  • 他花了整整两天时间,在“屎山”上“雕花”,终于在周五晚上9点提交了代码,并附上了一句:“修改了核心登录方法,请测试同学帮忙回归所有登录场景!

  • 上海(小葵):

  • 小葵微微一笑。她只做了三件事:

    • 新建一个类 DouyinLoginStrategy.java,实现 LoginStrategy 接口,在里面写好抖音登录的逻辑。

    • @Component 标注该类。

    • 提交代码。

  • UserService?一行代码都没改。

  • LoginStrategyFactory?一行代码都没改。

  • 她只花了半天时间就完成了开发和自测。她的测试报告是:“新增抖音登录,不影响任何原有登录逻辑,已通过单元测试。

对比:

张三是在“搬砖”,一块一块往上堆,代码越来越臃F肿,风险越来越高。

小葵是在“砌墙”,她定义了“砖块”(LoginStrategy接口)的“标准”,她搭建了“脚手架”(LoginStrategyFactory)。未来加功能,只是按标准生产“砖块”,然后放上去就行了。

张三的代码,是“功能的堆砌”;小葵的代码,是“可扩展的设计”。

核心要点:告别“功能堆砌”,拥抱“系统设计”

从小葵和张三的对比中,我们看到了“资深”与“初级”最核心的区别:

  • 初级(功能思维): 拿到需求,思考“我该如何实现它?”。(如张三,用if-else实现了功能)

  • 资深(系统思维): 拿到需求,思考“这是‘哪一类’问题?我该如何设计一个‘系统’,让‘这一类’所有问题都能被优雅地解决?”。(如小葵,把“登录”抽象为“策略”)

这种“系统思维”的基石,就是“高内聚、低耦合”的设计思想。

理论基础:“高内聚、低耦合”的设计灵魂

这是软件工程中被提及次数最多,但最难做到的原则。

  1. 高内聚 (High Cohesion):

    1. 定义: 把“相关”的功能,“内聚”到一个模块(类、方法)中。一个模块只做“一件事”,并且把“这件事”做好。

    2. 小葵的代码: WechatLoginStrategy 只负责微信登录,PasswordLoginStrategy 只负责密码登录。它们各自的“内聚性”非常高。

    3. 张三的代码: loginV2 方法负责了“所有”登录。它的内聚性极低,是一个“大杂烩”。

  2. 低耦合 (Low Coupling):

    1. 定义: 模块与模块之间,应尽量减少“依赖”。如果必须依赖,也应该依赖“抽象”(接口),而不是“具体”(实现)。

    2. 小葵的代码: UserService 根本“不认识” WechatLoginStrategy,它只“认识” LoginStrategy 这个“接口”。UserService 和具体的登录逻辑“解耦”了。

    3. 张三的代码: loginV2 方法“强耦合”了 WechatApiServiceUserDao 以及所有登录类型的实现细节。

可视化对比:

graph TD
    subgraph "张三的设计-高耦合, 低内聚"
        A(UserService.loginV2) --> B(微信登录逻辑)
        A --> C(密码登录逻辑)
        A --> D(访客登录逻辑)
        A --> E(抖音登录逻辑)
        
        B --> F(WechatApiService)
        C --> G(UserDao)
        E --> H(DouyinApiService)
    end
    
    subgraph "小葵的设计 (低耦合, 高内聚)"
        U(UserService.login) --> V(LoginStrategyFactory)
        V -- "getType()" --> W(<b>LoginStrategy 接口</b>)
        
        W <.--- X(PasswordLoginStrategy)
        W <.--- Y(WechatLoginStrategy)
        W <.--- Z(GuestLoginStrategy)
        W <.--- Z1(DouyinLoginStrategy)
        
        Y --> F2(WechatApiService)
        Z1 --> H2(DouyinApiService)
    end
    
    style V fill:#ffc,stroke:#333
    style W fill:#ffc,stroke:#333,stroke-width:2px

关键技能(一):SOLID原则 —— “可扩展”的“宪法”

“高内聚、低耦合”是“灵魂”,而SOLID原则,就是实现这一灵魂的“法律条文”。

  1. S - 单一职责原则 (Single Responsibility Principle):

    1. 定义: 一个类只应该有一个“引起它变化”的原因。

    2. 应用: 小葵的WechatLoginStrategy只因“微信登录逻辑变更”而变化。张三的loginV2方法,任何一种登录(微信、密码、抖音)的变更,都会导致它变化。

  2. O - 开闭原则 (Open/Closed Principle):

    1. 定义: 软件实体(类、模块、函数)应该对“扩展”开放,对“修改”关闭。

    2. 应用: 这是本篇最重要的原则。

      • 小葵的代码: 完美符合OCP。当“抖音登录”需求来了,她**“扩展”了系统(增加了**DouyinLoginStrategy**类),但“关闭”**了核心代码(UserServiceFactory无需修改)。

      • 张三的代码: 彻底违反OCP。当“抖音登录”需求来了,他必须**“修改”** loginV2 这个核心方法。

  3. L - 里氏替换原则 (Liskov Substitution Principle):

    1. 定义: 子类必须可以替换掉它们的父类(或接口)。

    2. 应用: UserService 可以无差别地使用PasswordLoginStrategyWechatLoginStrategy来替换LoginStrategy接口,而行为(登录)符合预期。

  4. I - 接口隔离原则 (Interface Segregation Principle):

    1. 定义: 不应强迫客户端依赖它们用不到的接口。

    2. 应用: LoginStrategy接口很“瘦”,只有一个login和一个getStrategyName,没有强加“注册”、“登出”等其他方法。

  5. D - 依赖倒置原则 (Dependency Inversion Principle):

    1. 定义: 高层模块不应依赖低层模块,两者都应依赖“抽象”。

    2. 应用: UserService(高层模块)不依赖WechatLoginStrategy(低层模块),它们都依赖LoginStrategy(抽象/接口)。这也是Spring IoC/DI(控制反转/依赖注入)的核心。

    关键技能(二):设计模式 —— “可扩展”的“蓝图”

    SOLID是“宪法”,设计模式就是在此基础上形成的“经典判例”和“建筑蓝图”。

  6. 策略模式 (Strategy Pattern):

  • 解决什么问题: 解决“做一件事有多种方式”的问题。

  • 定义: 定义一系列算法(策略),将它们一个个封装起来,并使它们可以相互替换。

  • 应用场景(必看):

    • 登录: (如本文)密码登录、微信登录、短信登录…

    • 支付: 支付宝支付、微信支付、银行卡支付…

    • 校验: 不同的风控规则、不同的参数校验器…

    • 路由: 不同的负载均衡算法(轮询、随机、哈希)…

1..*
implements
implements
implements
UserService
-LoginStrategyFactory factory
+login(request)
LoginStrategyFactory
-Map<String, LoginStrategy> strategyMap
+getStrategy(type)
«Interface»
LoginStrategy
+login(request)
+getStrategyName()
PasswordLoginStrategy
+login(request)
+getStrategyName()
WechatLoginStrategy
+login(request)
+getStrategyName()
DouyinLoginStrategy
+login(request)
+getStrategyName()
  1. 工厂模式 (Factory Pattern):
  • 解决什么问题: 解决“如何创建不同对象”的问题。

  • 定义: 将“创建对象”的逻辑封装起来,客户端只需告诉工厂“我想要什么”,而无需关心“它是怎么被造出来的”。

  • 应用场景:

    • 策略工厂: (如本文)根据type创建对应的Strategy

    • 数据库连接: 根据配置创建MySQLConnectionOracleConnection

    • 消息队列: 根据配置创建RocketMQProducerKafkaProducer

  1. 模板方法模式 (Template Method Pattern):
  • 解决什么问题: 解决“一件事的流程固定,但个别步骤不同”的问题。

  • 定义: 在一个抽象类中定义一个算法的“骨架”(模板),而将一些可变的步骤延迟到子类中实现。

  • 应用场景:

    • 举例: 假设“注册”流程是固定的:1. 校验参数 -> 2. 创建用户数据 -> 3. 发放新用户福利。

    • AbstractRegisterHandler(抽象类)定义了register()这个“模板方法”。

    • validateParams()createUserData()抽象的,由子类实现(微信注册和密码注册的校验逻辑不同)。

    • sendWelcomeBonus()具体的(所有注册都送10元券)。

classDiagram
    class AbstractRegisterHandler {
        +register(request)*
        #validateParams(request)$
        #createUserData(request)$
        #sendWelcomeBonus(userId)
    }
    note for AbstractRegisterHandler {
      register() {
        validateParams();
        user = createUserData();
        sendWelcomeBonus(user.id);
      }
    }
    class WechatRegisterHandler {
        +validateParams(request)
        +createUserData(request)
    }
    class PasswordRegisterHandler {
        +validateParams(request)
        +createUserData(request)
    }
    AbstractRegisterHandler <|-- WechatRegisterHandler
    AbstractRegisterHandler <|-- PasswordRegisterHandler

实战要点:在业务代码中“嗅出”模式的味道

你不需要为了用模式而用模式(过度设计),而应该在代码“变坏”时,用模式去“拯救”它。

  • 代码坏味道 (Code Smell): 巨大的 if-elseswitch

    • 诊断: 违反了“开闭原则”和“单一职责”。

    • 药方: 策略模式工厂模式

  • 代码坏味道: 多个类中有大量重复的“流程性”代码。

    • 诊断: 代码重复,流程僵化。

    • 药方: 模板方法模式

  • 代码坏味道: 一个类的构造函数参数列表长得离谱(new User(a,b,c,d,e,f...))。

    • 诊断: 对象创建逻辑太复杂。

    • 药方: 建造者模式 (Builder Pattern)

推荐书籍

  1. 《Head First 设计模式》 (Head First Design Patterns) - Eric Freeman & Elisabeth Robson
  • 核心内容与思想:

    • 它不是在“教模式”,而是在“教原则”。它把“开闭原则”、“依赖倒置”等SOLID原则作为主线,贯穿全书。

    • 它的核心是“封装变化”。全书都在教你一件事:找到系统中“会变化”的部分,把它“封装”起来,让它不影响“不变”的部分。

    • (小葵的登录案例,就是“封装”了“登录方式”这个“变化点”)

  1. 《重构:改善既有代码的设计》 (Refactoring: Improving the Design of Existing Code) - Martin Fowler
  • 核心内容与思想:

    • 代码坏味道 (Code Smells): 本书的精华。它系统性地总结了几十种“代码变坏”的“信号”,比如“过长的方法”(Long Method,张三的loginV2)、“过大的类”、“重复代码”等。

    • 重构手法 (Techniques): 针对每一种“坏味道”,它提供了“处方”——一系列安全、标准化的“重构手法”(如“提炼方法”、“用策略模式替换条件逻辑”)。

    • 这本书让你明白,好的设计不是“一次性”设计出来的,而是“持续重构”出来的。

结语

小葵的上海“流浪”之旅开始了。她通过“策略模式”,成功地为“用户中心”打下了坚实的地基。她不再是一个“功能实现者”,而是一个“系统设计者”。

而张三,依然在北京的“凤凰”单体上,用if-else奋战在需求一线。他没有被淘汰,但他正在被“固化”。他离“资深”的距离,不是技术的距离,而是“设计思维”的距离。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值