千里之堤,溃于蚁穴。
近几年部分时间在老系统平台上构建新项目,或者在新系统开发新项目。近年疫情原因,经济萧条,裁员较多,不得不参与处理别人遗留的项目。在同时开发新老系统的同时,我总结除了几种典型代码后期维护太耗时了,包括自己当时为了赶时间而完成的代码。
多年后再回去看这些坏代码,然后看了些别人优秀代码,总结了几点自己过去没处理好的代码或者其它开发者没处理好的代码。Java中常提到“低耦合高内聚”,为了后期代码质量的提高做了些总结。首先我总结了下业务中最多的代码问题。
![](https://img-blog.csdnimg.cn/img_convert/d7a8bdb43f1515d381acd7b2b50542ca.png)
1、随意依赖
弊端:代码耦合度高,无法抽离出独立的组件。
如:
public void sendMessage(String text){
//依赖具体的实现
String url=new FileManager().upload();
send(url,text);
}
这时候,我们可以通过提取接口,依赖稳定的抽象接口来进行解耦。解耦后再通过注入框架,将接口的实现注入到接口的调用类中。
IUpload iUpload;
public void sendMessage(String text) {
String url = iUpload.upload();
send(url, text);
}
解决方案:暴露接缝减少合或者依赖于稳定的抽象接口而不依赖具体的实现。
2、过度嵌套
弊端:代码阅读性差不方便维护。
如:使用多层级if()else{}
public void case0(){
boolean A = true,B=true,C=false,D=true,E=true,F=false,G=true,H=false,I=true,J=false;
if(A){
System.out.println("A");
if(B){
System.out.println("B");
if(C){
System.out.println("C");
}else if(D){
System.out.println("D");
if(E){
System.out.println("E");
}
}
}else if(F){
System.out.println("F");
if(G){
System.out.println("G");
}
}
}else if(H){
System.out.println("H");
if(I){
System.out.println("I");
if(J){
System.out.println("J");
}
}
}
}
你是不是觉得这段代码的逻辑层层嵌套,可读性很差?其实这就是过度嵌套带来的一个主要问题:代码阅读性差,不方便维护。此外,这种代码修改起来也非常容易出错,稍不注意就可能破坏之前的逻辑。那么如何简化过度嵌套的代码呢?最好的方式就是将逻辑拉平,我们不用在分支中来回切换上下文。将逻辑拉平的方式有多种,下面我们通过一些示例来看看。
boolean isA,isB,isC;
double getAmount() {
double result;
if (isA) result = adAmount();
else {
if (isB) result = bAmount();
else {
if (isC) result = cAmount();
else result = otherAmount();
};
}
return result;
}
于上面这个例子,我们可以通过“提前 Return”将这个示例的嵌套逻辑拉平(如下所示),你可以对比一下上下两段代码的阅读感受。
double getAmountRefactor() {
double result;
if (isA) return adAmount();
if (isB) return bAmount();
if (isC) return cAmount();
return otherAmount();
}
事前预防,远胜于事后解决,我们在平时的项目开发中就应该避免过度的代码嵌套。因此,我建议在代码合入之前先进行静态代码扫描,在扫描工具上设置合适的圈复杂度阈值(小于 10),一旦发现超过阈值,就提示错误,不让代码合并入库。
解决方案:将逻辑拉平,减少在分支中来回切换上下文
3、重复代码
弊端:当遇到变化时需要修改许多不同的类维护工作量翻倍。
如:
public class DuplicateCodeCopyCase {
String name;
String password;
public void login(){
if(name == null){
return;
}
if(password == null){
return;
}
phoneLogin();
}
public void Register(){
if(name == null){
return;
}
if(password == null){
return;
}
phoneRegister();
}
private void phoneLogin() {
}
private void phoneRegister() {
}
}
我们可以将共同逻辑提取成公共的方法,来减少重复代码。在上面的例子中,name 和 password 的判断就可以提取成公共的方法,具体如下所示。
public class DuplicateCodeCopyCase {
String name;
String password;
public void login(){
if (isInValid()) return;
phoneLogin();
}
private boolean isInValid() {
if (name == null) {
return true;
}
if (password == null) {
return true;
}
return false;
}
public void Register(){
if (isInValid()) return;
phoneRegister();
}
private void phoneLogin() {
}
private void phoneRegister() {
}
}
解决方案:将公共部分提取为方法或者类进行复用。
4、无效代码及资源
弊端:增加理解代码的成本降低代码的可维护性。
如:在整个项目中没有被任何代码所调用到的类、方法、变量或者资源问题。比如改了什么方法,然后开发者留下了之前的方法或者变量,可能是为了防止后面改回去。当然实际开发中确实也有需求改了几版最终回到初始版本。一般来说,编译工具在打包时会自动将这些无效的代码移除,不会导致应用的包体积增大。但是无效代码及资源在你编写代码的时候依旧会存在,这会增加代码的理解成本,降低代码的可维护性。
对于无效代码及资源,我们可以把静态代码扫描工具(Sonar、Lint 等)加入到流水线中及时检查。而对于僵尸代码,则需要加强人工的代码监视来应对。
解决方案:直接删除无效的代码及资源。
5、缺少抽象
弊端:代码可扩展性差所有的需求变化都要在一个类中进行集中修改,易出错。
如:在弄一个原生的APP时发现UI 操作、业务数据处理、网络操作、数据缓存等逻辑都在一个类中,还有我们业务有很多比较复杂,所有的逻辑都放一个类里,如下面这个
public class LoginActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
final EditText usernameEditText = findViewById(R.id.username);
final EditText passwordEditText = findViewById(R.id.password);
final Button loginButton = findViewById(R.id.login);
loginButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean isLoginSuccess = false;
String username = usernameEditText.getText().toString();
String password = passwordEditText.getText().toString();
boolean isUserNameValid;
if (username == null) {
isUserNameValid = false;
} else {
Pattern pattern = Pattern.compile("\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*");
Matcher matcher = pattern.matcher(username);
if (username.contains("@")) {
isUserNameValid = matcher.matches();
} else {
isUserNameValid = !username.trim().isEmpty();
}
}
if (!isUserNameValid || !(password != null && password.trim().length() > 5)) {
isLoginSuccess = false;
} else {
//通过服务器判断账户及密码的有效性
if (username.equals("123@163.com") && password.equals("123456")) {
//登录成功保存本地的信息
SharedPreferencesUtils.put(LoginActivity.this, username, password);
isLoginSuccess = true;
}
}
if (isLoginSuccess) {
//登录成功跳转主界面
startActivity(new Intent(LoginActivity.this, MainActivity.class));
} else {
//对登录失败进行提示
Toast.makeText(LoginActivity.this, "login failed", Toast.LENGTH_LONG).show();
}
}
});
}
}
这种问题的解决思路也很清晰,就是分而治之,将不同维度的代码独立开来,使其职责更加单一,这样有需求变化时就能独立演进了。我们实际业务也是这样,我看有的类里行数都超过两三千行,连看下去的欲望都没有。
解决方案:使用分层架构进行优化使其职责更加单一。