代码演进之路:从嵌套地狱到模块化设计
在软件开发过程中,我们常常会遇到一些"丑陋"的代码。这些代码可能功能完整,但结构混乱、难以维护。本文将通过一个Kubernetes部署更新策略的解析函数,展示代码如何从最初的嵌套结构,逐步演进到清晰模块化的设计。
第一阶段:原始嵌套代码
最初的代码实现是一个典型的嵌套条件结构,所有逻辑都集中在一个大函数中:
func (i *Inference) parseUpdateStrategy(ctx context.Context, task *entity.TaskInfoView, subTasks []*entity.SubTaskInfoView) error {
if i.deployment != nil && task.ExtraConfiguration.UpdateStrategy != nil {
updateType, ok := task.ExtraConfiguration.UpdateStrategy["type"]
if ok {
if updateType == "recreate" {
log.Debug(ctx, "策略类型为recreate")
i.deployment.Spec.Strategy.Type = appv1.RecreateDeploymentStrategyType
i.deployment.Spec.Strategy.RollingUpdate = nil
} else if updateType == "rollingUpdate" {
log.Debug(ctx, "策略类型为rollingUpdate")
i.deployment.Spec.Strategy.Type = appv1.RollingUpdateDeploymentStrategyType
i.deployment.Spec.Strategy.RollingUpdate = &appv1.RollingUpdateDeployment{
MaxUnavailable: func() *intstr.IntOrString {
if val := task.ExtraConfiguration.UpdateStrategy["maxUnavailable"]; val != "" {
parsed := intstr.Parse(val)
log.Debug(ctx, fmt.Sprintf("maxUnavailable解析成功: %+v", parsed))
return &parsed
}
return nil
}(),
MaxSurge: func() *intstr.IntOrString {
if val := task.ExtraConfiguration.UpdateStrategy["maxSurge"]; val != "" {
parsed := intstr.Parse(val)
log.Debug(ctx, fmt.Sprintf("maxSurge解析成功: %+v", parsed))
return &parsed
}
return nil
}(),
}
}
} else {
log.Error(ctx, "更新策略类型为空")
}
} else {
if i.deployment == nil {
log.Error(ctx, "deployment对象为空")
}
if task.ExtraConfiguration.UpdateStrategy == nil {
log.Error(ctx, "更新策略配置为空")
}
}
return nil
}
这段代码存在几个明显问题:
- 多层嵌套的if-else结构,可读性差
- 重复的配置解析逻辑
- 缺少明确的错误处理机制
- 所有逻辑耦合在一个函数中,难以扩展
第二阶段:函数拆分与模块化
第一次重构时,我们采用了简单的函数拆分方法:
func (i *Inference) parseUpdateStrategy(ctx context.Context, task *entity.TaskInfoView, subTasks []*entity.SubTaskInfoView) error {
// 参数校验
if i.deployment == nil {
return fmt.Errorf("deployment对象为空")
}
if task.ExtraConfiguration.UpdateStrategy == nil {
return fmt.Errorf("更新策略配置为空")
}
// 获取策略类型
updateType, ok := task.ExtraConfiguration.UpdateStrategy["type"]
if !ok {
return fmt.Errorf("更新策略类型为空")
}
// 应用对应的更新策略
log.Debug(ctx, fmt.Sprintf("应用更新策略: %s", updateType))
switch updateType {
case "recreate":
return i.applyRecreateStrategy(ctx, task.ExtraConfiguration.UpdateStrategy)
case "rollingUpdate":
return i.applyRollingUpdateStrategy(ctx, task.ExtraConfiguration.UpdateStrategy)
default:
return fmt.Errorf("不支持的更新策略类型: %s", updateType)
}
}
// 应用重建策略
func (i *Inference) applyRecreateStrategy(ctx context.Context, config map[string]string) error {
i.deployment.Spec.Strategy.Type = appv1.RecreateDeploymentStrategyType
i.deployment.Spec.Strategy.RollingUpdate = nil
return nil
}
// 应用滚动更新策略
func (i *Inference) applyRollingUpdateStrategy(ctx context.Context, config map[string]string) error {
i.deployment.Spec.Strategy.Type = appv1.RollingUpdateDeploymentStrategyType
i.deployment.Spec.Strategy.RollingUpdate = &appv1.RollingUpdateDeployment{
MaxUnavailable: parseUpdateConfigValue(ctx, config, "maxUnavailable"),
MaxSurge: parseUpdateConfigValue(ctx, config, "maxSurge"),
}
return nil
}
// 解析更新配置值
func parseUpdateConfigValue(ctx context.Context, config map[string]string, key string) *intstr.IntOrString {
if val, exists := config[key]; exists && val != "" {
parsed := intstr.Parse(val)
log.Debug(ctx, fmt.Sprintf("%s解析成功: %+v", key, parsed))
return &parsed
}
return nil
}
这次重构带来了以下改进:
- 提取了不同策略的处理逻辑到单独的方法
- 统一了参数校验,使用错误返回代替日志
- 抽取了配置解析的公共逻辑
- 使用switch语句代替嵌套if-else,结构更清晰
第三阶段:工厂模式与策略模式
随着系统复杂度增加,我们需要更灵活的设计:
// 更新策略接口
type UpdateStrategy interface {
Apply(deployment *appv1.Deployment, config map[string]string) error
}
// 重建策略实现
type RecreateStrategy struct{}
func (s *RecreateStrategy) Apply(deployment *appv1.Deployment, config map[string]string) error {
deployment.Spec.Strategy.Type = appv1.RecreateDeploymentStrategyType
deployment.Spec.Strategy.RollingUpdate = nil
return nil
}
// 滚动更新策略实现
type RollingUpdateStrategy struct{}
func (s *RollingUpdateStrategy) Apply(deployment *appv1.Deployment, config map[string]string) error {
deployment.Spec.Strategy.Type = appv1.RollingUpdateDeploymentStrategyType
deployment.Spec.Strategy.RollingUpdate = &appv1.RollingUpdateDeployment{
MaxUnavailable: parseUpdateConfigValue(config, "maxUnavailable"),
MaxSurge: parseUpdateConfigValue(config, "maxSurge"),
}
return nil
}
// 配置解析工具函数
func parseUpdateConfigValue(config map[string]string, key string) *intstr.IntOrString {
if val, exists := config[key]; exists && val != "" {
parsed := intstr.Parse(val)
return &parsed
}
return nil
}
// 更新策略工厂
type UpdateStrategyFactory struct{}
func (f *UpdateStrategyFactory) CreateStrategy(strategyType string) (UpdateStrategy, error) {
switch strategyType {
case "recreate":
return &RecreateStrategy{}, nil
case "rollingUpdate":
return &RollingUpdateStrategy{}, nil
default:
return nil, fmt.Errorf("不支持的策略类型: %s", strategyType)
}
}
// 重构后的解析函数
func (i *Inference) parseUpdateStrategy(ctx context.Context, task *entity.TaskInfoView, subTasks []*entity.SubTaskInfoView) error {
// 参数校验
if i.deployment == nil {
return fmt.Errorf("deployment对象为空")
}
if task.ExtraConfiguration.UpdateStrategy == nil {
return fmt.Errorf("更新策略配置为空")
}
// 获取策略类型
updateType, ok := task.ExtraConfiguration.UpdateStrategy["type"]
if !ok {
return fmt.Errorf("更新策略类型为空")
}
// 创建并应用策略
factory := &UpdateStrategyFactory{}
strategy, err := factory.CreateStrategy(updateType)
if err != nil {
return err
}
log.Debug(ctx, fmt.Sprintf("应用更新策略: %s", updateType))
return strategy.Apply(i.deployment, task.ExtraConfiguration.UpdateStrategy)
}
这次重构的优势:
- 完全解耦了策略实现与调用逻辑
- 引入了策略模式,便于添加新的策略类型
- 工厂模式封装了对象创建过程
- 提高了代码的可测试性和可扩展性
演进总结
从这个简单的函数演进过程,我们可以总结出一些代码优化的经验:
- 从小处着手:先进行简单的函数拆分,提高可读性
- 提取公共逻辑:识别重复代码并抽取为工具函数
- 应用设计模式:当需求复杂度增加时,引入适当的设计模式
- 持续重构:代码质量是持续改进的过程,不是一次性完成的
通过这种渐进式的重构方法,我们不仅提高了代码质量,还减少了引入新bug的风险。最终得到的代码结构清晰、易于维护,并且能够更好地适应未来的变化。