您的位置:首页 > 编程语言

如何写好代码中的函数?

2017-01-16 17:48 218 查看
辛苦堆砌,转载请注明出处,谢谢!

看到文章标题,一些程序员可能会惊讶了,函数还有好坏之分?我们天天都在写着不同的函数,面向过程编程中,函数是程序的基本组成部分,面向对象编程中,函数是类的方法,是与对象发送消息的接口。可以说函数是我们编程中经常使用语法结构。因此函数编写的好坏,直接关系到代码的质量。

由于函数是程序的组成单元,将代码逻辑封装为函数,可以极大限度的简化单个函数的逻辑成分,可以简化每一个函数的代码量,由于一个人能够同时关注的代码逻辑点是有限的,将不同的逻辑点封装为函数只需要几行代码,但是对于开发者,可以极大限度的将注意力放在这几行代码上,从而减少代码出错的概率。这个好处是毋庸置疑的。

但是过度的封装函数也不是很好的编码习惯,函数调用涉及到函数栈的资源分配,在一定程度上会影响软件的性能,另外,不能完整归纳为一个逻辑段的代码,一般不要封装为函数,否则反而会对代码的可读性造成不必要的损害。

因此我们封装函数时,一般要注意几点:

函数名称一定要有确切的含义,看到函数名,应该能够知道函数所做的事情。但是函数名也不应该太长,在函数名称过长时,可以采用缩写的形式,如command可以缩写为cmd。

函数的参数表不宜过长,过长的参数表往往预示着代码存在的“坏味道”,最常常表现出的两个问题就是(1)该函数不仅仅包含了一个逻辑点,或者说(2)本该将参数中的一些内容绑定为结构体或者类,但是我们没有这么做。如果是第一种情况,很简单,我们可以看看是不是可以调整代码结构,将这个参数表很长的函数进行分割。如果是第二种情况,我们可以进行重构,将一些变量捆绑在结构体或者一个类当中,将函数参数转换为传递结构体或者类的实例(对象)作为参数。

注意参数的顺序,由于函数的参数有输入参数也有输出参数——输入参数作为函数的输入使用,输出参数则是函数返回值不止一个时,我们用来获取其他的函数输出的指针变量。我们要按照规定的顺序安排参数,一般是输入参数放在参数表前面,输出参数放在后面,这样便于区分函数参数的作用。

输入参数尽可能避免直接拿来使用,而最好是在函数内部赋值给一个局部变量使用,必要的时候进行深拷贝,这样可以避免一类bug的发生,即函数的调用者通过指针传递输入参数,本以为函数不会修改指针指向的内容,调用函数后,继续使用这个指针,但是由于函数的开发者疏忽,直接使用了指针,导致函数调用者的代码逻辑出现问题。

基于防御式编程的建议,尽可能对函数的所有参数进行合法性校验,防止传入问题数据,导致函数出现问题。

尽可能控制函数的长度,保持函数短小精干。尽可能在重复代码较多的时候进行函数的封装,防止不必要的封装。

基于以上几点,我们可以写出更加“牢固”的函数,对外,函数的接口美观,使用方式清晰,对内,坏的数据无法进入,最终的结果就是我们的函数对函数调用者是一个严格的黑盒——传递给我合法的数据,交给你处理后的正确结果。

最后,基于函数,再给大家介绍一种自顶向下的开发方式,之前的文章有提到使用注释作为伪代码,那么现在更进一步,我们有些时候甚至于不需要伪代码,需要的是高质量的函数。我们假设一个场景,让机器人走一个正方形,提供起始点坐标和变长即可,我们编码可以这样进行,先写一个方法goSquare

public void goSquare(int startLeft, int startTop, int length) {
goRight(startLeft, startTop, length);
goDown(startLeft + length, startTop, length);
goLeft(startLeft + length, startTop + length, length);
goUp(startLeft, startTop + length, length);
}

我们让机器人向四个方向走四条直线,但是四个方向的方法我们还没有定义,那好,我们继续定义四个方法。

private void goRight(int startLeft, int startTop, int length) {
goLine("right", startLeft, startTop, length);
}

private void goLeft(int startLeft, int startTop, int length) {
goLine("left", startLeft, startTop, length);
}

private void goDown(int startLeft, int startTop, int length) {
goLine("down", startLeft, startTop, length);
}

private void goUp(int startLeft, int startTop, int length) {
goLine("up", startLeft, startTop, length);
}四个方法都是调用了goLine方法走一条支线,只是方向和起始点不同,我们再继续实现goLine方法。
private void goLine(
String direction, int startLeft, int startTop, int length) {
System.out.println("I will go " + direction + ", "
+ "start: (" + startLeft + ", " + startTop + ")");

int endX = startLeft, endY = startTop;
switch (direction) {
case "right":
endX = startLeft + length;
break;
case "down":
endY = startTop + length;
break;
case "left":
endX = startLeft - length;
break;
case "up":
endY = startTop - length;
break;
default:
break;
}

System.out.println("I am now at "
+ "(" + endX + ", " + endY + ")");
}
做了一个简单的模拟,打印开始点和终止点。最后是main函数

public static void main(String[] args) {
Robot robot = new Robot();
robot.goSquare(20, 30, 10);
}


输出结果为

I will go right, start: (20, 30)
I am now at (30, 30)
I will go down, start: (30, 30)
I am now at (30, 40)
I will go left, start: (30, 40)
I am now at (20, 40)
I will go up, start: (20, 40)
I am now at (20, 30)


例子很简单,主要是体会开发的步骤和思想,我们自顶向上,从业务逻辑逐渐细化,直到最后我们彻底完成了工作,编译也全部通过。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: