我对可重用性、可维护性的一点理解
一.写这篇博客的起因
起因是老师经常灌输的观点,终于有点感觉吧。。。然后就想到要写一篇关于我对代码可维护性和可重用性的一些理解,不过我的理解肯定非常简陋,毕竟学艺不精。
初学者有没有这样一种感觉,我们看书的时候,书上代码或者老师讲的代码什么意思我们都懂,这个地方为什么这样做我们也懂,这样做是为了可以方便重复使用,便于修改,也就是便于维护(这是我片面理解),但是一到我自己写代码,我自己都感觉自己的代码真的是个垃圾。可能部分考虑到了,但是整体还是不尽如人意,所以为了简单了解一下代码的可重用性和可维护性,给自己了写这篇博客的任务。
二.what?什么是代码的可重用性、可维护性
在别人的一篇博客上看到的,那篇博客说高质量代码的三要素:可读性、可维护性和可变更性。也有的博客说是可读性、可重用性和可扩展性,其实都是差不多的意思。
来自以下两篇博客:
https://www.cnblogs.com/Nancy771959506/p/11592510.html
https://blog.csdn.net/iamshaofa/article/details/99683091
※ 可重用性,Reusable
指一份代码可以直接拿到别的项目里、不加修改(或少量修改),即可重复使用。
※ 可扩展性, Extensible
指的是一份设计,不但可以满足现在的需求,更可以适应将来的变化。
这里主要是对后面两种高质量代码应该有的要素进行说明,其实第一种可读性,大家也都懂,你的变量的名字取的比较一眼就能看懂,注释简短精悍,代码逻辑清楚,可读性也就强了。
上面对于可重用性和可扩展性,在我自己看来就是你写的代码重复代码少,不可能出现大面积的复制粘贴,可以重复使用。
可扩展性我的理解是可维护性,也就是你写的代码要便于后期的修改和维护。
三.why?为什么要代码可重用性和可维护性强
其实,这里是我主要想总结的点,不仅仅是为什么。为什么其实也很简单,如果你的代码很多重复代码一直在复制粘贴,那你写的是冗余的代码,残废代码。仔细想一想,如果你代码重复过多,那么你代码是不是很长,自然很难在你的代码中找到关键的点,那么可读性也就很差。同时你的可维护性也很差,我感觉这三个元素是共通的关系。
举一个很简单的例子,比如,你写一个网页中的一个字体的颜色吧,一般在工作的时候,字体肯定很多吧,假如你每段都加一个属性就是字体的颜色,这是很重复吧,然后突然有一天,老板让你把字体颜色改以下,且不说你复制粘贴想想就很累,改颜色还得一个个的改,重复多,可维护性也自然而然就会变差。可以通过CSS样式的选择器来实现一对多。这就是为什么需要让自己代码高质量就必须注重这两个元素的原因
其实我的重点不在这里,我想列出来的是我所学知识的一点点总结,可能很简单,但是也挺能说明问题的。在How里面列举吧。
四.How?怎么样提升我们自己代码,增强可读性和可维护性
其实这个我也不知道,我只能将我自己所学的进行一个归纳,尽量在自己写代码的时候往这些方面去想。
1.我们接触代码肯定大多一开始都是学C语言开始的,那么
#include<stdio.h>
#include加头文件不正是实现了代码的重用性吗,最近有时间的时候看了几面《C和指针》,上面就有讲到
这种包含头文件和宏定义都是预处理文件。
什么是预处理?
预处理工作是系统引用预处理程序对源程序中的预处理部分做处理,而预处理部分是指以“#”开头的、放在函数之外的、一般放在源文件的前面的预处理命令,如:包括命令 #include,宏命令 #define 等,合理地利用预处理功能可以使得程序更加方便地阅读、修改、移植、调试等,也有利于模块化程序设计
拿上面这个代码来说,意思就是预处理器用户叫stdio.h的库函数头文件的内容替换了#include指令语句,其结果就仿佛是stdio.h的内容被逐字逐句的写到源文件的位置,你想想,你只需要这一行代码调用库函数头文件就相当于减少了多少重复的代码,那么库函数头文件这样,我们扩展一下,我们为什么需要函数?不是一样的道理吗?我们不停的调用函数,这不正是重用性和实现模块分离的意思吗?
《C和指针》有一个建议:假如你的这个程序的源代码由几个源文件所组成,那么使用该函数的源文件都必须写明该函数的原型,把原型放在头文件中并且使用#include指令包含它们,可以避免由于同一个声明的多份拷贝而导致的维护性的问题
甚至,你可以把你需要用到的源文件,整到一个单独的文件去编写这些源文件的声明,这个也是这本书推荐的技巧之一,下面是原话
《C和指针》技巧:如果你有一些声明需要用到几个不同的源文件,这个技巧也是一种方便的方法—你在一个单独的文件中编写这些声明,然后用#include指令把这个文件包含到需要使用这些声明的源文件中,这样,你就只需要这些声明的一份拷贝,用不着在许多不同的地方进行复制,避免了在维护这些代码时出现错误的可能性。
2.#define 变量名+常量
其实这个也是一个预处理指令,#define是宏命令,他把名字定义为后面那个常量,当这个变量以后出现在源文件的任何地方的时候,他就会被替换为名字,不过需要注意的是,由于它们被定义为字面值常量的话,这些名字一般都大写,用于提醒它们并非普通的变量。
那我首先来说一下变量,为什么要有变量?我只浅陋的说其中的一个原因,比如width=20;这个width其实就是20,但是系统为他分了一个内存单元,赋予了新的意义,也就是宽度,是宽度为20,所以当你在修改代码的时候,或者是别人看的代码的时候就能很容易的知道了这个变量代表的什么意思,而不是生硬的20这个数字
而宏定义的名字,也就是固定不变的,可以这么理解吧,也就是说当你在后期修改自己代码的时候,用到这个名字的代码修改是不是一个个的修改很麻烦,那么你直接修改这个宏定义变量的值,所有值都修改好了,这不正是增强了代码的可维护性吗?
既然这里讲到了#define宏定义,那么java中定义一个
public static final +数据类型 + 变量名;
这是一样的道理阿,甚至如果这种变量很多的话,你还可以自己写一个类,然后在这个类里面去定义静态不变常量,外面的类去调用。感觉起到的都是一样的效果
同时使用宏定义也会带来一些副作用
参考:https://blog.csdn.net/shantf93/article/details/79767311
(1).首先你要把宏定义的概念了解清楚,宏定义是预编译阶段,直接由预编译解释器,不是变量,是把后面的内容用名字替换了,所以就会带来一些优先级问题
比如:
-
传入变量优先级
#define JIUZHE(a,b) a / b
JIUZHE(1+2,3)=>1+2/3,其实我们想要的是(1+2)/3
就是直接把内容替换过来了 -
作为值返回时,类似1)
#define ADD(a,b) (a) + (b)
int c = ADD(a,b) * 3; => (a) + (b) * 3 其实是想要(a + b) * 3
所以给出建议:
宏里面参数全部用括号括起来;如果作为值返回,整个表达式也用括号括起来 。
也就是说必须里面的输入进去必须是一个整体
所以,上面最好这么写:
#define JIUZHE(a,b) ((a) / (b))
#define ADD(a,b) ((a) + (b))
(2).同名的问题导致的,可能方法传进去的参数顺序不同但是明明相同会出现一些错误,所以采取下划线的命名
#define HASH(str,sz,rst) do{unsigned int n = 0; n = xxx; rst = n % sz;}while(0)
这是一个hash的宏实现,其中定义了一个临时变量n,根据str计算n,然后对sz求模并把返回值赋给传进来的rst.
这么调用:
int n;
HASH(“hello”,7,n);
不会达到改变n的效果,因为实际使用参数n和宏内部的变量n同名。
给出建议:
宏内部变量使用一种不同风格的命名方式。
比如:
#define HASH(str,sz,rst) do{unsigned int __n = 0; __n = …
(3).就是宏定义名字后面的内容在内部执行多少次就会自增或者自减多少次,所以最好在内部只使用一次
#define MAX(a,b) ((a) > (b) ? (a) : (b))
int a = 3,b = 2;
结果输出的结果确实a = 5,c = 4
原因是: 在宏内部一个变量"执行"多少次,它就自增或自减了多少次。
给出建议:
所以一般使用宏最好不要传入自增自减 。
(4).实际使用的时候要和宏定义内部定义的数据类型要一一对应
3.Java Web中CSS的选择器
CSS是专门为了和html分离开修饰样式的,有时候重复属性太多,通过选择器存入属性值,来修饰目标元素样式的属性
这个之前就有讲,这里不细说
(1)标记选择器
标记选择器是指用HTML标记名作为选择器,按标记名称进行分类
(2)类选择器
通过元素的class属性来进行分类
(3)id选择器
通过具体的id指定特定的元素
(4)通配选择器
匹配页面所有的元素
其实这些,我都不是很在意,我觉得这些都可以看成是函数,你懂吧,然后你理解成里面有什么算法,然后你要改特定目标的属性值的时候是不是用到这些选择器,那你就当成调用函数,不是一样的意思吗?这是我的理解,可能有问题,不过上面讲的例子也可以说明可以增强代码的可维护性。
4.JavaWeb Servlet的RequestDispatcher接口的请求包含方法
请求包含指的是使用include()方法将Servlet请求转发给其他Web资源进行处理,与请求转发不同的是,在请求包含返回的响应消息中,既包含了当前Servlet的响应消息,也包含了其他Web资源做出的响应消息。
上面都是书上的概念,随便看看就行,接下来说一下我的理解,就比如你做一个网站,你是一家公司,是不是公司一开始都有自己的图标是吧,甚至你还可以搞个主菜单进行点击跳转,而且主菜单栏不变,还有就是你结尾可能是有自己的公司版本标志,然后你要搞很多网站,这些上面那一部分和下面一部分都不变的时候,你如果写每个网站都这么写是不是有很多重复代码,你就可以使用Include()方法,请求包含,也就是将这一部分Servlet不想做或者做不了的内容用Include()方法给其他的Web资源做,用的时候直接通过RequestDispatcher的对象调用include(request,response)方法就可以达到另外Web资源处理的代码。是不是代码的重复就变少了,也就利于维护了。因为所有重复的东西都只需要其他Web资源处理一次就好了,别的直接调用,这就是调用函数阿。
下面是include()方法的工作原理
5.Java类的继承
在我的理解,父类有的是系统给你写好的放在一些Library里面,通过import导入包,你想自定义一个类的时候,但是主体功能还是大致一样的时候就可以选择用继承,继承不是也少了很多重复的代码吗,因为你的子类的很多属性或者方法都可以直接继承父类,不用再重复的写了,但是需要注意的是,子类继承必须重写父类的所有抽象方法,所以类的继承也是可以提高代码的重用性
五.总结
这次写的还是挺累的,基本没什么代码纯文本内容,总结的还是一些比较基础的内容,总结这个的目的,主要是相当于一个手册吧,以后出现一个例子就往里面补充,不断的补充,然后变成手册,到时候写代码也能尽量的往这方面去想,也讲了为什么写代码要考虑那三个因素。总之,想让我们代码变得优秀,那三个因素是必不可少的。
推荐阅读