千万不要把 bool 设计成函数参数
我们有很多Coding Style 或 代码规范。但这一条可能会经常被我们所遗忘,就是我们经常会在函数的参数里使用bool参数,这会大大地降低代码的可读性。不信?我们先来看看下面的代码。
当你读到下面的代码,你会觉得这个代码是什么意思?
widget->repaint(false);
是不要repaint吗?还是别的什么意思?看了文档后,我们才知道这个参数是immediate, 也就是说,false代表不立即重画,true代码立即重画。
Windows API中也有这样一个函数:InvalidateRect,当你看到下面的代码,你会觉得是什么意思?
InvalidateRect(hwnd, lpRect, false);
我们先不说InvalidateRect这个函数名取得有多糟糕,我们先说一下那个false参数?invalidate意为“让XXX无效”,false是什么意思?双重否定?是肯定的意思?如果你看到这样的代码,你会相当的费解的。于是,你要去看一下文档,或是InvalidateRect的函数定义,你会看到那个参数是 BOOL bErase,意思是,是否要重画背景。
这样的事情有很多,再看下面的代码,想把str中的”%USER%”替换成真实的用户名:
str.replace("%USER%", user, false); // Qt 3
TNND,那个false是什么意思?不替换吗?还是别的什么意思,看了文档才知道,false代码大小写不敏感的替换。
其实,如果你使用枚举变量/常量,而不是bool变量,你会让你的代码更易读,如:
widget->repaint(PAINT::immediate); widget->repaint(PAINT::deffer); InvalidateRect(hwnd, lpRect, !RepantBackground); str.replace("%USER%", user, Qt::CaseInsensitive); // Qt 4
如果对这个事不以为然的话,我们再来看一些别的示例,你不妨猜猜看看下面的代码:
component.setCentered(true, false);
这什么玩意儿啊?看了文档你才知道,这原来是 setCentered(centered, autoUpdate);
new Textbox(300, 100, false, true);
这又是什么啊?看了文档才知道,这是创建一个文本框,第三个参数是是否要滚动条,第四个是是否要自动换行。TNND。
上面的情况还不算最差,看看下面的双重否定。
component.setDisabled(false); filter.setCaseInsensitive(false)
再来一个,如果你读到下面的代码,相信你会和我一样,要么石化了,要么凌乱了。
event.initKeyEvent("keypress", true, true, null, null, false, false, false, false, 9, 0);
看完这篇文章,我希望你再也不要把bool为作为函数参数了。除非两个原因:
- 你100%确认不会带来阅读上的问题,比如Java的 setVisible (bool).
- 你100%确认你想去写出无法维护很难阅读的代码。
【更新2011/9/8】当然,别的参数也会有一样的问题,比如:new Textbox(300, 100, false, true);
中的300 和 100,不知道是坐标还是长宽,只不过,一般长度或坐标这样的参数都不会被hard code,都会有变量名,而bool这种参数经常性地被传成true 和 false。 bool参数表现得更为明显一些罢了。
所以,程序中不要出现magic number,true/false 也是一种 magic number。但是,我想告诉大家,从API设计的角度来说,你无法强制调用者用常量来取代true/false,定义成枚举类型是最好的选择。
最后,如果你想设计一个好的API,强烈推荐你读一下Nokia的Qt的《API Design Principles》,本文就是其中的“Boolean Trap”。
(全文完)
(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)
《千万不要把 bool 设计成函数参数》的相关评论
objective-c 的参数描述概念完美地解决了这个问题……
MFC里面那个UpdateData(BOOL)才经典, 每次我都记不得TRUE是哪边的数据同步到哪边, 写了2年都记不住, 每次都要去msdn看看
习惯使用枚举常量。
new Textbox(300, 100, false, true);怎么判断300,100是长宽,还是出现坐标?
所以说有时候文档是必须的吧
没错,别的参数也会有一样的问题,不过没有bool那么明显。另外,别的参数是可以传变量的,bool参数一般在被调用的时候就是true或false
以前还真没注意到这个问题,受教了
凌乱的那个boolean和其他类型一样都是个悲剧,位置而不是名字作为参数指定是问题的本质。如果这个语言语法类似与func(XXX = a, YYY = b, TTT = c)可读性会提高很多,或者
para.setXXX
para.setYYY
func(para)
同理解决doSomthing(true) 的问题.
但是这种方式某些情况下会丢失参数组合的描述性。比如setXXXYYYZZZ会一起set,但是setBBBAAA要一起set。那么就可能要重新抽出新的或者子的para之类。如果重载函数就比较方便func(XXX, YYY,ZZZ) 和 func(BBB,AAA)…因此还是前面把参数名字写出来的语法比较好。
原因还是人比较懒。。
至于set func传boolean一般不会有什么可读性问题吧。
所以Zend Studio写PHP这点儿还是很方便的。。所有API都会有提示,直接告诉你bool的意义。。当然如果是自己的API需要写一下注释
QT的widget对象的setEnable不是就用的bool做参数么。。。
还有MFC的UpdateData!
Obj-C 毫无压力
一楼说的没错,objective-c不存在这个问题
[component setCentered:YES autoUpdate:NO];
那个repaint 还就是QT::QWidget的私货呢
Objective-c 表示毫无压力。
哎呀 被一楼说了 看了开头我就想说objc解决了这个问题 不过对于坑爹的[]语法我表示很不理解
Python 毫无压力地表示传带关键字的 bool 参数很易读。
Python keyword argument无压力
深有同感,这个函数用起来太不爽啦!
js 的 Event 对象 有个 initMouseEvent 方法:
evt.initMouseEvent(“click”, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
怎么样,毫无压力吧
《.NET设计规范》这本书里面也讲过这个问题,在.NET 2.0以后的框架都没有这个问题了,这本书貌似出到第二版了。感觉这本书讲了很多API的设计实践经验,很不错。
JavaScript里有常量这个概念吗??
其实都是要看文档的… 如果看了文档的话用bool和enum又有什么区别呢?
只是enum的代码可读性更好吧
为了提高可读性,一般先声明一个bool并赋值,然后当参数传入。
// 原始代码
widget->repaint(false);
// 改进过的代码
bool immediate= false;
widget->repaint(immediate);
你看,如果你这样出的乱子就更多了。人家immediate是true,结果你用一个本来是true的变量传递进了false。呵呵,你这样还不如直接用true/false呢。用常量、枚举,都可以。
楼主其实想表达不要写magic value,要用常量。标题只是用于吸引大家眼球
从API的设计角度来说的话,这样做就很不好,因为你无法强制调用API的人都要用常量。所以,直接定义成enum会是最好的。
原来作者已在更新中说明这个观点,我太浮躁了,看了前面几行就发了评论。。。
参数值的形式并不是为函数名准备的,它只是一个参数,所以在编辑器中加入了 Java Doc 这样的东东。一般阅读也不会把参数当做函数语义的一部分(Object-C 这样的语言除外)。
哈哈,这个真的很经典
@陈皓
对,应该定义成常量的,而不是变量。直接写true/false还是不可取,这样不利以后的代码阅读。
真是受够了windows API中的这类函数了,真是烦死了,看一段代码还得查看好多次文档,烦都烦死了,还哪有心情看代码啊!
嗯,用bool做参数不符合最小意外原则
个人觉得并不完全是由于使用bool类型做参数导致代码难度的,比如上面的例子
str.replace(“%USER%”, user, false);
如果改成
bool caseSensitive =false;
str.replace(“%USER%”,user,caseSensitive);
就可读了.
事实上只要是直接使用一个具体的值做函数参数都可能导致代码不易读.举一个很简单的例子,对于以下代码:
DateTime dt = new DateTime(1,2,3);
如果不是由于Visual Studio的提示功能,一眼看上去也很难看出到底哪个数字是年哪个是月,哪个是日.
看来,你还是没有明白为什么要使用枚举!
1)使用变量转入true/false并不是强制,在所有用到这个函数的地方使用变量太麻烦了。
2)使用变量带来的问题是,如果你给caseSensitive符一个false的值,那你的代码就完蛋了。
3)使用常量好一点。但是你无法强制调用者使用常量。
关于你举的DateTime dt = new DateTime(1,2,3);的例子,那是Magic Number,与本例无关。
bool变量只有true/false状态,适合定义成枚举。
google c++代码规范里面也提到这一点,这种情况应该加注释,或者用一些常量等
http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Implementation_Comments#Implementation_Comments
gtk里也好多bool的参数
Python也不错啊!调用时可以使用参数名的
@cndj
同感啊,太坑爹了
凡事都不能太绝对了.bool作为参数可能会带来一些理解上的歧义,但也不能完全否定了bool的作用.要不就该取消这个类型了.我觉得还是把握住作者的一些底线要求吧.
看完这篇文章,我希望你再也不要把bool为作为函数参数了。除非两个原因:
1.你100%确认不会带来阅读上的问题,比如Java的 setVisible (bool).
2.你100%确认你想去写出无法维护很难阅读的代码。
类似的这种情况.只要你自己确定自己要传的就是bool,完全可以坚持自己的意见.
python 有 命名参数,很多语言也有,如果
widget->repaint(immediate=false);
这种形式是被强制的,那就没有问题了。
事实上,在命名参数的情形下,参数名确实是强制的,C语言只是不支持强制命名参数而已。
同感!很痛恨让人一头雾水的”true/false”,不看函数原型(对于文档差的代码,甚至得读源码)很难立刻读懂其含义。
个人觉得靠读代码理解本来就不合理
@纯属吐槽
同意,最讨厌这种一棒子打死的建议了……
对,对这种能列出的尽量要列出来,就像windows的窗口样式要不是定义成宏谁也不知是个什么意思,或至少是看了一次下次就有大概判断了,当然像一些其它的数据型变量是没法。以前记得在哪本书上看到过。@陈皓
同意不能一棒打死,个人觉得其实是函数的命名是否配得上这些个 BOOL
且如:event.initKeyEvent(“keypress”, true, true, null, null, false, false, false, false, 9, 0);
这个其实也不能说true/false有啥问题,诸侯的9/0/keypress,不看函数原型也没人知道啥意义的
6779fd1ce请教一下博主,我的站被K了,怎么才能恢复呢?
天生這項語言就是如此,不要給他奇怪的解法了
要適應它 或 不用他
Uncle Bob的某次presentation中就提到过这个
怪不得有些文档,总是看不明白呢?原来还有这个原因呢啊!
我把你的这篇blog转载了,注明了出处
@freetstar
因为 bool 减少了代码量。
那解决方案是怎么样的呢?
这个可能是因为有了true false之后,而且效率不是问题,有许多人认为C的按位运行不再直观之后带来的么?
C中一个字节的控制参数8个位,就相当8个布尔值了。囧