代码之丑(第二期)

在日常编程中,优雅的代码总是千篇一律,而"丑陋"的代码确是千奇百怪,"代码之丑"第二期,一起来看看吧

一、大类

如果一个类,代码杂糅在一起,你一眼看上去就不想读,这大概率是个"大类"(这里的类特指实体类)。

大类的产生

  • 职责不单一

    arduino 复制代码
    public class User {
      private long userId;
      private String name;
      private String nickname;
      private String email;
      private String phoneNumber;
      private AuthorType authorType;
      private ReviewStatus authorReviewStatus;
      private EditorType editorType;
      ...
    }

    上述代码,将用户字段、作者字段、编辑字段揉在一起,但是他们并不是并行字段,一个人不能是普通用户并同时拥有作者字段和编辑字段,这样后续再有不同职责的"用户"也会加入到这个类里面,造成大类的产生。可以把 UserAuthorEditor 三个类拆开来,这样就会避免大类的产生,并且使用 userId 做关联

    arduino 复制代码
    public class User {
      private long userId;
      private String name;
      private String nickname;
      private String email;
      private String phoneNumber;
      ...
    }
    
    public class Author {
      private long userId;
      private AuthorType authorType;
      private ReviewStatus authorReviewStatus;
      ...
    }
    
    public class Editor {
      private long userId;
      private EditorType editorType;
      ...
    }
  • 字段未分组

    arduino 复制代码
    public class User {
      private long userId;
      private String name;
      private String nickname;
      private String email;
      private String phoneNumber;
      ...
    }

    修改后的 User 类还可以继续拆分,根据类的不同信息做分组,例如将用户主体信息与联系方式信息拆分

    arduino 复制代码
    public class User {
      private long userId;
      private String name;
      private String nickname;
      private Contact contact;
      ...
    }
    
    public class Contact {
      private String email;
      private String phoneNumber;
      ...
    }

二、长参数

我们应该写短的代码,逻辑单一明确,看上去不冗余。

聚沙成塔

arduino 复制代码
public void createBook(final String title, 
                       final String introduction,
                       final URL coverUrl,
                       final BookType type,
                       final BookChannel channel,
                       final String protagonists,
                       final String tags,
                       final boolean completed) {
  ...
  Book book = Book.builder
    .title(title) 
    .introduction(introduction)
    .coverUrl(coverUrl)
    .type(type)
    .channel(channel)
    .protagonists(protagonists)
    .tags(tags)
    .completed(completed)
    .build();
    
  this.repository.save(book);
}

上述代码将一堆作品相关的参数揉在一起,那么我之后增加一个作品相关的参数时必然会直接在方法后面加,这样会导致参数列表非常庞大且不好维护。解决的方法是封装,这里参数都是跟作品相关的,所以封装一个作品类,然后使用该类作为参数即可

typescript 复制代码
public void createBook(final NewBookParamters parameters) {
  ...
}

public class NewBookParamters {
  private String title;
  private String introduction;
  private URL coverUrl;
  private BookType type;
  private BookChannel channel;
  private String protagonists;
  private String tags;
  private boolean completed;
  ...
}

但是这样的话使用 parameters 时需要调用一堆 get 方法,需要把他们一个个取出来,就像这样

scss 复制代码
public void createBook(final NewBookParamters parameters) {
  ...
  Book book = Book.builder
    .title(parameters.getTitle()) 
    .introduction(parameters.getIntroduction())
    .coverUrl(parameters.getCoverUrl())
    .type(parameters.getType())
    .channel(parameters.getChannel())
    .protagonists(parameters.getProtagonists())
    .tags(parameters.getTags())
    .completed(parameters.isCompleted())
    .build();
    
  this.repository.save(book);
}

站在设计的角度,我们这里引入的是一个新的模型,一个模型的封装应该是以行为为基础的。现在模型产生了,我们需要给他配套的行为,这个封装类的行为应该是构建一个作品出来,所以:

typescript 复制代码
public class NewBookParamters {
  private String title;
  private String introduction;
  private URL coverUrl;
  private BookType type;
  private BookChannel channel;
  private String protagonists;
  private String tags;
  private boolean completed;
  
  public Book newBook() {
    return Book.builder
      .title(title) 
      .introduction(introduction)
      .coverUrl(coverUrl)
      .type(type)
      .channel(channel)
      .protagonists(protagonists)
      .tags(tags)
      .completed(completed)
      .build();
  }
}

这样创建作品的业务逻辑完成了简化,而作品本身作为一个模型也具备了其行为

java 复制代码
public void createBook(final NewBookParamters parameters) {
  ...
  Book book = parameters.newBook();
    
  this.repository.save(book);
}

动静分离

考虑参数间的变化频率是否一致

并不是所有参数都可以关联到同个模型,下方代码中 bookId 是频繁变动的书籍 id,每次请求都不一样,而 httpClientprocessor 很有可能是静态的,每次请求都携带相同的参数,这时候就可以将静态参数提取成所在类的变量,注意要考虑具体业务来判断该参数是否静态。

ini 复制代码
public void getChapters(final long bookId, 
                        final HttpClient httpClient,
                        final ChapterProcessor processor) {
  HttpUriRequest request = createChapterRequest(bookId);
  HttpResponse response = httpClient.execute(request);
  List<Chapter> chapters = toChapters(response);
  processor.process(chapters);
}

改变之后:

ini 复制代码
public void getChapters(final long bookId) {
  HttpUriRequest request = createChapterRequest(bookId);
  HttpResponse response = this.httpClient.execute(request);
  List<Chapter> chapters = toChapters(response);
  this.processor.process(chapters);
}

告别标记

apporved 参数标记了是否审核通过,根据这个标记会有不同的处理逻辑。在实际代码中,你是愿意看到分类明确、业务逻辑单一的代码,还是愿意看到一堆标记需要你排列组合、甚至标记注释都没有的代码?

注意:标记不只 boolean,还有 int、枚举、String 的魔法值等等,如果一个标记代表了情况 A、情况 B,那当情况 C 发生的时候是否还需要继续耦合在这一个方法里?

arduino 复制代码
public void editChapter(final long chapterId, 
                        final String title, 
                        final String content, 
                        final boolean apporved) {
  ...
}

解决标记,最简单的就是将标记代表的不同路径拆分,或者你可以提取他们的公共模块,所以下述代码有可能可以分成三块:公共逻辑、普通编辑、直接通过的编辑

arduino 复制代码
// 普通的编辑,需要审核
public void editChapter(final long chapterId, 
                        final String title, 
                        final String content) {
  ...
}

// 直接审核通过的编辑
public void editChapterWithApproval(final long chapterId,
                                    final String title,
                                    final String content) {
 ...
}

三、总结

在业务开发中,我们经常会为了业务便利而定义"大类"、使用长参数、使用标记,这看起来并没什么不好的,或者你会说"业务开发就是这样的,功能没问题就可以了,谁会管写法呢?",但我仍然认为遵守一定的代码规范并养成习惯,这是一个程序员的职业

相关推荐
Asthenia04121 分钟前
深入剖析 Spring Cloud Feign 的 Contract 组件:设计与哲学
后端
Asthenia041210 分钟前
Feign 原理:Client 的实现与选型(ApacheHttpClient/OkHttp)/Feign超时控制
后端
开心就好202511 分钟前
Flutter实战】文本组件及五大案例
后端
Ai 编码助手14 分钟前
Golang并发编程:Data Race检测与解决方案
开发语言·后端·golang
宦如云20 分钟前
Assembly语言的嵌入式调试
开发语言·后端·golang
日月星辰Ace26 分钟前
SpringBootTest 不能使用构造器注入
java·spring boot
Gvemis⁹38 分钟前
Scala总结(三)
开发语言·后端·scala
一只小闪闪40 分钟前
langchain4j搭建失物招领系统(五)---实现失物登记功能-大模型流式输出
java·人工智能·后端
SimonKing44 分钟前
Kafka 4.0.0震撼来袭,彻底摒弃Zookeeper
java·后端·架构
Csss1 小时前
[抖音]用户首页分享链接获取视频数据,可选解析视频文案
后端