<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>API | 酷 壳 - CoolShell</title>
	<atom:link href="https://coolshell.cn/tag/api/feed" rel="self" type="application/rss+xml" />
	<link>https://coolshell.cn</link>
	<description>享受编程和技术所带来的快乐 - Coding Your Ambition</description>
	<lastBuildDate>Sun, 21 Apr 2019 08:48:59 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.2</generator>
	<item>
		<title>API设计原则 &#8211; Qt官网的设计实践总结</title>
		<link>https://coolshell.cn/articles/18024.html</link>
					<comments>https://coolshell.cn/articles/18024.html#comments</comments>
		
		<dc:creator><![CDATA[李 鼎]]></dc:creator>
		<pubDate>Tue, 25 Jul 2017 06:16:30 +0000</pubDate>
				<category><![CDATA[C/C++语言]]></category>
		<category><![CDATA[技术读物]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[系统架构]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[api-design]]></category>
		<category><![CDATA[API设计]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Coding]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[qt]]></category>
		<category><![CDATA[程序员]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=18024</guid>

					<description><![CDATA[<p>（感谢好友 @李鼎 翻译此文） 原文链接：API Design Principles &#8211; Qt Wiki 基于Gary的影响力上 Gary Gao ...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/18024.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/18024.html">API设计原则 – Qt官网的设计实践总结</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><span style="color: #993300;"><strong>（感谢好友 <a href="http://www.weibo.com/oldratlee" target="_blank"  rel="noopener noreferrer">@李鼎</a> 翻译此文）</strong></span></p>
<p>原文链接：<a href="http://qt-project.org/wiki/API-Design-Principles">API Design Principles</a> &#8211; <a href="http://wiki.qt.io/">Qt Wiki</a><br />
基于<a href="http://blog.csdn.net/gaoyingju">Gary的影响力</a>上 <em>Gary Gao</em> 的译文稿：<a href="http://blog.csdn.net/gaoyingju/article/details/8245108">C++的API设计指导</a></p>
<h2>译序</h2>
<p><img decoding="async" loading="lazy" class="alignright size-medium" src="https://coolshell.cn/wp-content/uploads/2017/07/api-design-300x278.jpg" alt="" width="300" height="278" /></p>
<p>Qt的设计水准在业界很有口碑，一致、易于掌握和强大的API是Qt最著名的优点之一。此文既是Qt官网上的API设计指导准则，也是Qt在API设计上的实践总结。虽然Qt用的是C++，但其中设计原则和思考是具有普适性的（如果你对C++还不精通，可以忽略与C++强相关或是过于细节的部分，仍然可以学习或梳理关于API设计最有价值的内容）。整个篇幅中有很多示例，是关于API设计一篇难得的好文章。</p>
<p>需要注意的是，这篇Wiki有一些内容并不完整，所以，可能会有一些阅读上的问题，我们对此做了一些相关的注释。</p>
<p>PS：翻译中肯定会有不足和不对之处，欢迎评论&amp;交流；另译文源码在<a href="https://github.com/oldratlee/translations/tree/master/api-design-principles-from-qt">GitHub的这个仓库</a>中，可以<a href="https://github.com/oldratlee/translations/issues">提交Issue</a>/<a href="https://github.com/oldratlee/translations/fork">Fork后提交代码</a>来建议/指正。</p>
<h1>API设计原则</h1>
<p>一致、易于掌握和强大的API是Qt最著名的优点之一。此文总结了我们在设计Qt风格API的过程中所积累的诀窍（know-how）。其中许多是通用准则；而其他的则更偏向于约定，遵循这些约定主要是为了与已有的API保持一致。</p>
<p>虽然这些准则主要用于对外的API（public API），但在设计对内的API（private API）时也推荐遵循相同的技巧（techniques），作为开发者之间协作的礼仪（courtesy）。</p>
<p><span id="more-18024"></span></p>
<p>如有兴趣也可以读一下 <em>Jasmin Blanchette</em> 的<a href="http://www4.in.tum.de/~blanchet/api-design.pdf">Little Manual of API Design (PDF)</a> 或是本文的前身 <em>Matthias Ettrich</em> 的<a href="https://doc.qt.io/archives/qq/qq13-apis.html">Designing Qt-Style C++ APIs</a>。</p>
<h1>1. 好API的6个特质</h1>
<p>API之于程序员就如同图形界面之于普通用户（end-user）。API中的『P』实际上指的是『程序员』（Programmer），而不是『程序』（Program），强调的是API是给程序员使用的这一事实。</p>
<p>在第13期<a href="http://doc.qt.io/archives/qq/">Qt季刊</a>，<em>Matthias</em> 的<a href="https://doc.qt.io/archives/qq/qq13-apis.html">关于API设计的文章</a>中提出了观点：API应该极简（minimal）且完备（complete）、语义清晰简单（have clear and simple semantics）、符合直觉（be intuitive）、易于记忆（be easy to memorize）和引导API使用者写出可读代码（lead to readable code）。</p>
<h2>1.1 极简</h2>
<p>极简的API是指每个class的public成员尽可能少，public的class也尽可能少。这样的API更易理解、记忆、调试和变更。</p>
<h2>1.2 完备</h2>
<p>完备的API是指期望有的功能都包含了。这点会和保持API极简有些冲突。如果一个成员函数放在错误的类中，那么这个函数的潜在用户就会找不到，这也是违反完备性的。</p>
<h2>1.3 语义清晰简单</h2>
<p>就像其他的设计一样，我们应该遵守最少意外原则（the principle of least surprise）。好的API应该可以让常见的事完成的更简单，并有可以完成不常见的事的可能性，但是却不会关注于那些不常见的事。解决的是具体问题；当没有需求时不要过度通用化解决方案。（举个例子，在Qt 3中，<code>QMimeSourceFactory</code>不应命名成<code>QImageLoader</code>并有不一样的API。）</p>
<h2>1.4 符合直觉</h2>
<p>就像计算机里的其他事物一样，API应该符合直觉。对于什么是符合直觉的什么不符合，不同经验和背景的人会有不同的看法。API符合直觉的测试方法：经验不很丰富的用户不用阅读API文档就能搞懂API，而且程序员不用了解API就能看明白使用API的代码。</p>
<h2>1.5 易于记忆</h2>
<p>为使API易于记忆，API的命名约定应该具有一致性和精确性。使用易于识别的模式和概念，并且避免用缩写。</p>
<h2>1.6 引导API使用者写出可读代码</h2>
<p>代码只写一次，却要多次的阅读（还有调试和修改）。写出可读性好的代码有时候要花费更多的时间，但对于产品的整个生命周期来说是节省了时间的。</p>
<p>最后，要记住的是，不同的用户会使用API的不同部分。尽管简单使用单个Qt类的实例应该符合直觉，但如果是要继承一个类，让用户事先看好文档是个合理的要求。</p>
<h1>2. 静态多态</h1>
<p>相似的类应该有相似的API。在继承（inheritance）合适时可以用继承达到这个效果，即运行时多态。然而多态也发生在设计阶段。例如，如果你用<code>QProgressBar</code>替换<code>QSlider</code>，或是用<code>QString</code>替换<code>QByteArray</code>，你会发现API的相似性使的替换很容易。这即是所谓的『静态多态』（static polymorphism）。</p>
<p>静态多态也使记忆API和编程模式更加容易。因此，一组相关的类有相似的API有时候比每个类都有各自的一套API更好。</p>
<p>一般来说，在Qt中，如果没有足够的理由要使用继承，我们更倾向于用静态多态。这样可以减少Qt public类的个数，也使刚学习Qt的用户在翻看文档时更有方向感。</p>
<h2>2.1 好的案例</h2>
<p><code>QDialogButtonBox</code>与<code>QMessageBox</code>，在处理按钮（<code>addButton()</code>、<code>setStandardButtons()</code>等等）上有相似的API，不需要继承某个<code>QAbstractButtonBox</code>类。</p>
<h2>2.2 差的案例</h2>
<p><code>QTcpSocket</code>与<code>QUdpSocket</code>都继承了<code>QAbstractSocket</code>，这两个类的交互行为的模式（mode of interaction）非常不同。似乎没有什么人以通用和有意义的方式用过<code>QAbstractSocket</code>指针（或者 <strong><em>能</em></strong> 以通用和有意义的方式使用<code>QAbstractSocket</code>指针）。</p>
<h2>2.3 值得斟酌的案例</h2>
<p><code>QBoxLayout</code>是<code>QHBoxLayout</code>与<code>QVBoxLayout</code>的父类。好处：可以在工具栏上使用<code>QBoxLayout</code>，调用<code>setOrientation()</code>使其变为水平/垂直。坏处：要多一个类，并且有可能导致用户写出这样没什么意义的代码，<code>((QBoxLayout *)hbox)-&gt;setOrientation(Qt::Vertical)</code>。</p>
<h1>3. 基于属性的API</h1>
<p>新的Qt类倾向于用『基于属性（property）的API』，例如：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
QTimer timer;<br />
timer.setInterval(1000);<br />
timer.setSingleShot(true);<br />
timer.start();<br />
[/code]</p>
<p>这里的 <strong><em>属性</em></strong> 是指任何的概念特征（conceptual attribute），是对象状态的一部分 —— 无论它是不是<code>Q_PROPERTY</code>。在说得通的情况下，用户应该可以以任何顺序设置属性，也就是说，属性之间应该是正交的（orthogonal）。例如，上面的代码可以写成：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
QTimer timer;<br />
timer.setSingleShot(true);<br />
timer.setInterval(1000);<br />
timer.start();<br />
[/code]</p>
<blockquote><p>【译注】：正交性是指改变某个特性而不会影响到其他的特性。<a href="https://book.douban.com/subject/5387402/">《程序员修炼之道》</a>中讲了关于正交性的一个直升飞机坠毁的例子，讲得深入浅出很有画面感。</p></blockquote>
<p>为了方便，也写成：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
timer.start(1000)；<br />
[/code]</p>
<p>类似地，对于<code>QRegExp</code>会是这样的代码：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
QRegExp regExp;<br />
regExp.setCaseSensitive(Qt::CaseInsensitive);<br />
regExp.setPattern(&quot;.&quot;);<br />
regExp.setPatternSyntax(Qt::WildcardSyntax);<br />
[/code]</p>
<p>为实现这种类型的API，需要借助底层对象的懒创建。例如，对于<code>QRegExp</code>的例子，在不知道模式语法（pattern syntax）的情况下，在<code>setPattern()</code>中去解释<code>"."</code>就为时过早了。</p>
<p>属性之间常常有关联的；在这种情况下，我们必须小心处理。思考下面的问题：当前的风格（style）提供了『默认的图标尺寸』属性 vs. <code>QToolButton</code>的『<code>iconSize</code>』属性：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
toolButton-&gt;setStyle(otherStyle);<br />
toolButton-&gt;iconSize();    // returns the default for otherStyle<br />
toolButton-&gt;setIconSize(QSize(52, 52));<br />
toolButton-&gt;iconSize();    // returns (52, 52)<br />
toolButton-&gt;setStyle(yetAnotherStyle);<br />
toolButton-&gt;iconSize();    // returns (52, 52)<br />
[/code]</p>
<p>提醒一下，一旦设置了<code>iconSize</code>，设置就会一直保持，即使改变当前的风格。这 <strong><em>很好</em></strong>。但有的时候需要能重置属性。有两种方法：</p>
<ol>
<li>传入一个特殊值（如<code>QSize()</code>、<code>-1</code>或者<code>Qt::Alignment(0)</code>）来表示『重置』</li>
<li>提供一个明确的重置方法，如<code>resetFoo()</code>和<code>unsetFoo()</code></li>
</ol>
<p>对于<code>iconSize</code>，使用<code>QSize()</code>（比如 <code>QSize(–1, -1)</code>）来表示『重置』就够用了。</p>
<p>在某些情况下，getter方法返回的结果与所设置的值不同。例如，虽然调用了<code>widget-&gt;setEnabled(true)</code>，但如果它的父widget处于disabled状态，那么<code>widget-&gt;isEnabled()</code>仍然返回的是<code>false</code>。这样是OK的，因为一般来说就是我们想要的检查结果（父widget处于disabled状态，里面的子widget也应该变为灰的不响应用户操作，就好像子widget自身处于disabled状态一样；与此同时，因为子widget记得在自己的内心深处是enabled状态的，只是一直等待着它的父widget变为enabled）。当然诸如这些都必须在文档中妥善地说明清楚。</p>
<h1>4. C++相关</h1>
<h2>4.1 值 vs. 对象</h2>
<h3>4.1.1 指针 vs. 引用</h3>
<p>指针（pointer）还是引用（reference）哪个是最好的输出参数（out-parameters）？</p>
<p>[code language=&#8221;cpp&#8221;]<br />
void getHsv(int *h, int *s, int *v) const;<br />
void getHsv(int &amp;h, int &amp;s, int &amp;v) const;<br />
[/code]</p>
<p>大多数C++书籍推荐尽可能使用引用，基于一个普遍的观点：引用比指针『更加安全和优雅』。与此相反，我们在开发Qt时倾向于指针，因为指针让用户代码可读性更好。比较下面例子：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
color.getHsv(&amp;h, &amp;s, &amp;v);<br />
color.getHsv(h, s, v);<br />
[/code]</p>
<p>只有第一行代码清楚表达出<code>h</code>、<code>s</code>、<code>v</code>参数在函数调用中非常有可能会被修改。</p>
<p>这也就是说，编译器并不喜欢『出参』，所你应该在新的API中避免使用『出参』，而是返回一个结构体，如下所示：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
struct Hsv { int hue, saturation, value };<br />
Hsv getHsv() const;<br />
[/code]</p>
<blockquote><p>【译注】：函数的『入参』和『出参』的混用会导致 API 接口语义的混乱，所以，使用指针，在调用的时候，实参需要加上“&amp;”，这样在代码阅读的时候，可以看到是一个『出参』，有利于代码阅读。（但是这样做，在函数内就需要判断指针是否为空的情况，因为引用是不需要判断的，所以，这是一种 trade-off）</p>
<p>另外，如果这样的参数过多的话，最好使用一个结构体来把数据打包，一方面，为一组返回值取个名字，另一方面，这样有利用接口的简单。</p></blockquote>
<h3>4.1.2 按常量引用传参 vs. 按值传参</h3>
<p>如果类型大于16字节，按常量引用传参。</p>
<p>如果类型有重型的（non-trivial）拷贝构造函数（copy-constructor）或是重型的析构函数（destructor），按常量引用传参以避免执行这些函数。</p>
<p>对于其它的类型通常应该按值传参。</p>
<p>示例：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
void setAge(int age);<br />
void setCategory(QChar cat);<br />
void setName(QLatin1String name);</p>
<p>// const-ref is much faster than running copy-constructor and destructor<br />
void setAlarm(const QSharedPointer&lt;Alarm&gt; &amp;alarm);</p>
<p>// QDate, QTime, QPoint, QPointF, QSize, QSizeF, QRect<br />
// are good examples of other classes you should pass by value.<br />
[/code]</p>
<blockquote><p>【译注】：这是传引用和传值的差别了，因为传值会有对像拷贝，传引用则不会。所以，如果对像的构造比较重的话（换句话说，就是对像里的成员变量需要的内存比较大），这就会影响很多性能。所以，为了提高性能，最好是传引用。但是如果传入引用的话，会导致这个对象可能会被改变。所以传入const reference。</p></blockquote>
<h2>4.2 虚函数</h2>
<p>在C++中，当类的成员函数声明为virtual，主要是为了通过在子类重载此函数能够定制函数的行为。将函数声明为virtual的目的是为了让对这个函数已有的调用变成执行实际实例的代码路径。对于没有在类外部调用的函数声明成virtual，你应该事先非常慎重地思考过。</p>
<p>[code language=&#8221;cpp&#8221;]<br />
// QTextEdit in Qt 3: member functions that have no reason for being virtual<br />
virtual void resetFormat();<br />
virtual void setUndoDepth( int d );<br />
virtual void setFormat( QTextFormat *f, int flags );<br />
virtual void ensureCursorVisible();<br />
virtual void placeCursor( const QPoint &amp;pos;, QTextCursor **c = 0 );<br />
virtual void moveCursor( CursorAction action, bool select );<br />
virtual void doKeyboardAction( KeyboardAction action );<br />
virtual void removeSelectedText( int selNum = 0 );<br />
virtual void removeSelection( int selNum = 0 );<br />
virtual void setCurrentFont( const QFont &amp;f );<br />
virtual void setOverwriteMode( bool b ) { overWrite = b; }<br />
[/code]</p>
<p><code>QTextEdit</code>从Qt 3移植到Qt 4的时候，几乎所有的虚函数都被移除了。有趣的是（但在预料之中），并没有人对此有大的抱怨，为什么？因为Qt 3没用到<code>QTextEdit</code>的多态行为 —— 只有你会；简单地说，没有理由去继承<code>QTextEdit</code>并重写这些函数，除非你自己调用了这些方法。如果在Qt在外部你的应用程序你需要多态，你可以自己添加多态。</p>
<blockquote><p>【译注】：『多态』的目的只不过是为了实践 —— 『依赖于接口而不是实现』，也就是说，接口是代码抽像的一个非常重要的方式（在Java/Go中都有专门的接口声明语法）。所以，如果没有接口抽像，使用『多态』的意义也就不大了，因为也就没有必要使用『虚函数』了。</p></blockquote>
<h3>4.2.1 避免虚函数</h3>
<p>在Qt中，我们有很多理由尽量减少虚函数的数量。每一次对虚函数的调用会在函数调用链路中插入一个未掌控的节点（某种程度上使结果更无法预测），使得bug修复变得更复杂。用户在重写的虚函数中可以做很多疯狂的事：</p>
<ul>
<li>发送事件</li>
<li>发送信号</li>
<li>重新进入事件循环（例如，通过打开一个模态文件对话框）</li>
<li>删除对象（即触发『<code>delete this</code>』）</li>
</ul>
<p>还有其他很多原因要避免过度使用虚函数：</p>
<ul>
<li>添加、移动或是删除虚函数都带来二进制兼容问题（binary compatibility/BC）</li>
<li>重载虚函数并不容易</li>
<li>编译器几乎不能优化或内联（inline）对虚函数的调用</li>
<li>虚函数调用需要查找虚函数表（v-table），这比普通函数调用慢了2到3倍</li>
<li>虚函数使得类很难按值拷贝（尽管也可以按值拷贝，但是非常混乱并且不建议这样做）</li>
</ul>
<p>经验告诉我们，没有虚函数的类一般bug更少、维护成本也更低。</p>
<p>一般的经验法则是，除非我们以这个类作为工具集提供而且有很多用户来调用某个类的虚函数，否则这个函数九成不应该设计成虚函数。</p>
<blockquote><p>【译注】：</p>
<ol>
<li>使用虚函数时，你需要对编译器的内部行为非常清楚，否则，你会在使用虚函数时，觉得有好些『古怪』的问题发生。比如在创建数组对象的时候。</li>
<li>在C++中，会有一个基础类，这个基础类中已经实现好了很多功能，然后把其中的一些函数放给子类去修改和实现。这种方法在父类和子类都是一组开发人员维护时没有什么问题，但是如果这是两组开发人员，这就会带来很多问题了，就像Qt这样，子类完全无法控制，全世界的开发人员想干什么就干什么。所以，子类的代码和父类的代码在兼容上就会出现很多很多问题。所以，还是上面所说，其实，虚函数应该声明在接口的语义里（这就是设计模式的两个宗旨——依赖于接口，而不是实现；钟爱于组合，而不是继承。也是为什么Java和Go语言使用interface关键字的原因，C++在多态的语义上非常容易滥用）</li>
</ol>
</blockquote>
<h3>4.2.2 虚函数 vs. 拷贝</h3>
<p>多态对象（polymorphic objects）和值类型的类（value-type classes）两者很难协作好。</p>
<p>包含虚函数的类必须把析构函数声明为虚函数，以防止父类析构时没有清理子类的数据，导致内存泄漏。</p>
<p>如果要使一个类能够拷贝、赋值或按值比较，往往需要拷贝构造函数、赋值操作符（<code>operator =</code>）和相等操作符（<code>operator ==</code>）。</p>
<p>[code language=&#8221;cpp&#8221;]<br />
class CopyClass {<br />
public:<br />
    CopyClass();<br />
    CopyClass(const CopyClass &amp;other);<br />
    ~CopyClass();<br />
    CopyClass &amp;operator =(const CopyClass &amp;other);<br />
    bool operator ==(const CopyClass &amp;other) const;<br />
    bool operator !=(const CopyClass &amp;other) const;</p>
<p>    virtual void setValue(int v);<br />
};<br />
[/code]</p>
<p>如果继承<code>CopyClass</code>这个类，预料之外的事就已经在代码时酝酿了。一般情况下，如果没有虚成员函数和虚析构函数，就不能创建出可以多态的子类。然而，如果存在虚成员函数和虚析构函数，这突然变成了要有子类去继承的理由，而且开始变得复杂了。<strong><em>起初认为只要简单声明上虚操作符重载函数（virtual operators）。</em></strong> 但其实是走上了一条混乱和毁灭之路（破坏了代码的可读性）。看看下面的这个例子：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
class OtherClass {<br />
public:<br />
    const CopyClass &amp;instance() const; // 这个方法返回的是什么？可以赋值什么？<br />
};<br />
[/code]</p>
<p>（这部份还未完成）</p>
<blockquote><p>【译注】：因为原文上说，这部份并没有完成，所以，我也没有搞懂原文具体也是想表达什么。不过，就标题而言，原文是想说，在多态的情况下拷贝对象所带来的问题？？</p></blockquote>
<h2>4.3 关于const</h2>
<p><strong><em>C++的关键词const表明了内容不会改变或是没有副作用。可以应用于简单的值、指针及指针所指的内容，也可以作为一个特别的属性应用于类的成员函数上，表示成员函数不能修改对象的状态。</em></strong></p>
<p>然而，const本身并没有提供太大的价值 —— 很多编程语言甚至没有类似const的关键词，但是却并没有因此产生问题。实际上，如果你不用函数重载，并在C++源代码用搜索并删除所有的const，几乎总能编译通过并且正常运行。尽量让使用的const保持实用有效，这点很重要。</p>
<p>让我们看一下在Qt的API设计中与const相关的场景。</p>
<h3>4.3.1 输入参数：const指针</h3>
<p>有输入指针参数的const成员函数，几乎总是const指针参数。</p>
<p>如果函数声明为const，意味着既没有副作用，也不会改变对象的可见状态。那为什么它需要一个没有const限定的输入参数呢？记住const类型的函数通常被其他const类型的函数调用，接收到的一般都是const指针（只要不主动const_cast，我们推荐尽量避免使用const_cast）</p>
<p>以前：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
bool QWidget::isVisibleTo(QWidget *ancestor) const;<br />
bool QWidget::isEnabledTo(QWidget *ancestor) const;<br />
QPoint QWidget::mapFrom(QWidget *ancestor, const QPoint &amp;pos) const;<br />
[/code]</p>
<p><code>QWidget</code>声明了许多非const指针输入参数的const成员函数。注意，这些函数可以修改传入的参数，不能修改对象自己。使用这样的函数常常要借助const_cast转换。如果是const指针输入参数，就可以避免这样的转换了。</p>
<p>之后：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
bool QWidget::isVisibleTo(const QWidget *ancestor) const;<br />
bool QWidget::isEnabledTo(const QWidget *ancestor) const;<br />
QPoint QWidget::mapFrom(const QWidget *ancestor, const QPoint &amp;pos) const;<br />
[/code]</p>
<p>注意，我们在<code>QGraphicsItem</code>中对此做了修正，但是<code>QWidget</code>要等到Qt 5:</p>
<p>[code language=&#8221;cpp&#8221;]<br />
bool isVisibleTo(const QGraphicsItem *parent) const;<br />
QPointF mapFromItem (const QGraphicsItem *item, const QPointF &amp;point) const;<br />
[/code]</p>
<h3>4.3.2 返回值：const值</h3>
<p>调用函数返回的非引用类型的结果，称之为右值（R-value）。</p>
<p>非类（non-class）的右值总是无cv限定类型（cv-unqualified type）。虽然从语法上讲，加上const也可以，但是没什么意义，因为鉴于访问权限这些值是不能改变的。多数现代编译器在编译这样的代码时会提示警告信息。</p>
<blockquote><p>【译注】：cv-qualified的类型（与cv-unqualified相反）是由const或者volatile或者volatile const限定的类型。详见<a href="http://en.cppreference.com/w/cpp/language/cv">cv (const and volatile) type qualifiers &#8211; C++语言参考</a></p></blockquote>
<p>当在类类型（class type）右值上添加const关键字，则禁止访问非const成员函数以及对成员的直接操作。</p>
<p>不加const则没有以上的限制，但几乎没有必要加上const，因为右值对象生存时间（life time）的结束一般在C++清理的时候（通俗的说，下一个分号地方），而对右值对象的修改随着右值对象的生存时间也一起结束了（也就是本条语句的执行完成的时候）。</p>
<p>示例：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
struct Foo {<br />
    void setValue(int v) { value = v; }<br />
    int value;<br />
};</p>
<p>Foo foo() {<br />
    return Foo();<br />
}</p>
<p>const Foo cfoo() {<br />
    return Foo();<br />
}</p>
<p>int main() {<br />
    // The following does compile, foo() is non-const R-value which<br />
    // can&#8217;t be assigned to (this generally requires an L-value)<br />
    // but member access leads to a L-value:<br />
    foo().value = 1; // Ok, but temporary will be thrown away at the end of the full-expression.</p>
<p>    // The following does compile, foo() is non-const R-value which<br />
    // can&#8217;t be assigned to, but calling (even non-const) member<br />
    // function is fine:<br />
    foo().setValue(1); // Ok, but temporary will be thrown away at the end of the full-expression.</p>
<p>    // The following does _not_compile, foo() is &#8221;const&#8221; R-value<br />
    // with const member which member access can&#8217;t be assigned to:<br />
    cfoo().value = 1; // Not ok.</p>
<p>    // The following does _not_compile, foo() is &#8221;const&#8221; R-value,<br />
    // one cannot call non-const member functions:<br />
    cfoo().setValue(1); // Not ok<br />
}<br />
[/code]</p>
<blockquote><p>【译注】：上述的代码说明，如果返回值不是const的，代码可以顺利编译通过，然而并没有什么卵用，因为那个临时对像马上就被抛弃了。所以，这样的无用的代码最好还是在编译时报个错，以免当时头脑发热想错了，写了一段没用但还以为有用的代码。</p></blockquote>
<h3>4.3.3 返回值：非const的指针还是有const的指针</h3>
<p>谈到const函数应该返回非const的指针还是const指针这个话题时，多数人发现在C++中关于『const正确性』（const correctness）在概念上产生了分歧。 <em>问题起源是：<strong>const函数本身不能修改对象自身的状态，却可以返回成员的非const指针</strong>。返回指针这个简单动作本身既不会影响整个对象的可见状态，当然也不会改变这个函数职责范围内涉及的状态。但是，这却使得程序员可以间接访问并修改对象的状态。</em></p>
<p>下面的例子演示了通过返回非const指针的const函数绕开const约定（constness）的诸多方式中的一种：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
QVariant CustomWidget::inputMethodQuery(Qt::InputMethodQuery query) const {<br />
    moveBy(10, 10); // doesn&#8217;t compile!<br />
    window()-&gt;childAt(mapTo(window(), rect().center()))-&gt;moveBy(10, 10); // compiles!<br />
}<br />
[/code]</p>
<p>返回const指针的函数正是保护以避免这些（可能是不期望的/没有预料到的）副作用，至少是在一定程度上。但哪个函数你会觉得更想返回const指针，或是不止一个函数？</p>
<p>若采用const正确（const-correct）的方法，每个返回某个成员的指针（或多个指向成员的指针）的const函数必须返回const指针。在实践中，很不幸这样的做法将导致无法使用的API：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
QGraphicsScene scene;<br />
// … populate scene</p>
<p>foreach (const QGraphicsItem *item, scene.items()) {<br />
    item-&gt;setPos(qrand() % 500, qrand() % 500); // doesn&#8217;t compile! item is a const pointer<br />
}<br />
[/code]</p>
<p><code>QGraphicsScene::items()</code>是一个const函数，顺着思考看起来这个函数只应该返回const指针。</p>
<p>在Qt中，我们几乎只有非const的使用模式。我们选择的是实用路子： 相比滥用非const指针返回类型带来的问题，返回const指针更可能招致过分使用const_cast的问题。</p>
<h3>4.3.4 返回值：按值返回 还是 按const引用返回？</h3>
<p>若返回的是对象的拷贝，那么返回const引用是更直接的方案； 然而，这样的做法限制了后面想要对这个类的重构（refactor）。 （以<code>d-point</code>的典型做法（idiom）为例，我们可以在任何时候改变Qt类在内存表示（memory representation）；但却不能在不破坏二进制兼容性的情况下把改变函数的签名，返回值从<code>const QFoo &amp;</code>变为<code>QFoo</code>。） 基于这个原因，除去对运行速度敏感（speed is critical）而重构不是问题的个别情形（例如，<code>QList::at()</code>），我们一般返回<code>QFoo</code>而不是<code>const QFoo &amp;</code>。</p>
<blockquote><p>【译注】：参看《Effective C++》中条款23：Don&#8217;t try to return a reference when you must return an object</p></blockquote>
<h3>4.4.5 const vs. 对象的状态</h3>
<p>const正确性（Const correctness）的问题就像C圈子中vi与emacs的讨论，因为这个话题在很多地方都存在分歧（比如基于指针的函数）。</p>
<p>但通用准则是const函数不能改变类的可见状态。『状态』的意思是『自身以及涉及的职责』。这并不是指非const函数能够改变自身的私有成员，也不是指const函数改变不了。而是指函数是活跃的并存在可见的副作用（visible side effects）。const函数一般没有任何可见的副作用，比如：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
QSize size = widget-&gt;sizeHint(); // const<br />
widget-&gt;move(10, 10); // not const<br />
[/code]</p>
<p>代理（delegate）负责在其它对象上绘制内容。 它的状态包括它的职责，因此包括在哪个对象做绘制这样的状态。 调用它的绘画行为必然会有副作用； 它改变了它绘制所在设备的外观（及其所关联的状态）。鉴于这些，<code>paint()</code>作为const函数并不合理。 进一步说，任何<code>paint()</code>或<code>QIcon</code>的<code>paint()</code>的视图函数是const函数也不合理。 没有人会从内部的const函数去调用<code>QIcon::paint()</code>，除非他想显式地绕开const这个特性。 如果是这种情况，使用const_cast会更好。</p>
<p>[code language=&#8221;cpp&#8221;]<br />
// QAbstractItemDelegate::paint is const<br />
void QAbstractItemDelegate::paint(QPainter **painter, const QStyleOptionViewItem &amp;option, const QModelIndex &amp;index) const</p>
<p>// QGraphicsItem::paint is not const<br />
void QGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem option, QWidget *widget)<br />
[/code]</p>
<p>const关键字并不能按你期望的样子起作用。应该考虑将其移除而不是去重载const/非const函数。</p>
<h1>5. API的语义和文档</h1>
<p>当传值为<code>-1</code>的参数给函数，函数会是什么行为？有很多类似的问题……</p>
<p>是警告、致命错误还是其它？</p>
<p>API需要的是质量保证。API第一个版本一定是不对的；必须对其进行测试。 以阅读使用API的代码的方式编写用例，且验证这样代码是可读的。</p>
<p>还有其他的验证方法，比如</p>
<ul>
<li>让别人使用API（看了文档或是先不看文档都可以）</li>
<li>给类写文档（包含类的概述和每个函数）</li>
</ul>
<h1>6. 命名的艺术</h1>
<p>命名很可能是API设计中最重要的一个问题。类应该叫什么名字？成员函数应该叫什么名字？</p>
<h2>6.1 通用的命名规则</h2>
<p>有几个规则对于所有类型的命名都等同适用。第一个，之前已经提到过，不要使用缩写。即使是明显的缩写，比如把<code>previous</code>缩写成<code>prev</code>，从长远来看是回报是负的，因为用户必须要记住缩写词的含义。</p>
<p>如果API本身没有一致性，之后事情自然就会越来越糟；例如，Qt 3 中同时存在<code>activatePreviousWindow()</code>与<code>fetchPrev()</code>。恪守『不缩写』规则更容易地创建一致性的API。</p>
<p>另一个时重要但更微妙的准则是在设计类时应该保持子类名称空间的干净。在Qt 3中，此项准则并没有一直遵循。以<code>QToolButton</code>为例对此进行说明。如果调用<code>QToolButton</code>的 <code>name()</code>、<code>caption()</code>、<code>text()</code>或者<code>textLabel()</code>，你觉得会返回什么？用Qt设计器在<code>QToolButton</code>上自己先试试吧：</p>
<ul>
<li><code>name</code>属性是继承自<code>QObject</code>，返回内部的对象名称，用于调试和测试。</li>
<li><code>caption</code>属性继承自<code>QWidget</code>，返回窗口标题，对<code>QToolButton</code>来说毫无意义，因为它在创建的时候parent就存在了。</li>
<li><code>text</code>函数继承自<code>QButton</code>，一般用于按钮。当<code>useTextLabel</code>不为<code>true</code>，才用这个属性。</li>
<li><code>textLabel</code>属性在<code>QToolButton</code>内声明，当<code>useTextLabel</code>为<code>true</code>时显示在按钮上。</li>
</ul>
<p>为了可读性，在Qt 4中<code>QToolButton</code>的<code>name</code>属性改成了<code>objectName</code>，<code>caption</code>改成了<code>windowTitle</code>，删除了<code>textLabel</code>属性因为和<code>text</code>属性相同。</p>
<p>当你找不到好的命名时，写文档也是个很好方法：要做的就是尝试为各个条目（item）（如类、方法、枚举值等等）写文档，并用写下的第一句话作为启发。如果找不到一个确切的命名，往往说明这个条目是不该有的。如果所有尝试都失败了，并且你坚信这个概念是合理的，那么就发明一个新名字。像widget、event、focus和buddy这些命名就是在这一步诞生的。</p>
<blockquote><p>【译注】：写文档是一个非常好的习惯。写文档的过程其实就是在帮你梳理你的编程思路。很多时候，文档写着写着你就会发现要去改代码去了。除了上述的好处多，写文档还有更多的好处。比如，在写文档的过程中，你发现文字描述过于复杂了，这表明着你的代码或逻辑是复杂的，这就倒逼你去重构你的代码。所以 —— <strong>写文档其实就是写代码</strong>。</p></blockquote>
<h2>6.2 类的命名</h2>
<p>识别出类所在的分组，而不是为每个类都去找个完美的命名。例如，所有Qt 4的能感知模型（model-aware）的item view，类后缀都是<code>View</code>（<code>QListView</code>、<code>QTableView</code>、<code>QTreeView</code>），而相应的基于item（item-based）的类后缀是<code>Widget</code>（<code>QListWidget</code>、<code>QTableWidget</code>、<code>QTreeWidget</code>）。</p>
<h2>6.3 枚举类型及其值的命名</h2>
<p>声明枚举类型时，需要记住在C++中枚举值在使用时不会带上类型（与Java、C#不同）。下面的例子演示了枚举值命名得过于通用的危害：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
namespace Qt<br />
{<br />
    enum Corner { TopLeft, BottomRight, &#8230; };<br />
    enum CaseSensitivity { Insensitive, Sensitive };<br />
    &#8230;<br />
};</p>
<p>tabWidget-&gt;setCornerWidget(widget, Qt::TopLeft);<br />
str.indexOf(&quot;$(QTDIR)&quot;, Qt::Insensitive);<br />
[/code]</p>
<p>在最后一行，<code>Insensitive</code>是什么意思？命名枚举类型的一个准则是在枚举值中至少重复此枚举类型名中的一个元素：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
namespace Qt<br />
{<br />
    enum Corner { TopLeftCorner, BottomRightCorner, &#8230; };<br />
    enum CaseSensitivity { CaseInsensitive, CaseSensitive };<br />
    &#8230;<br />
};</p>
<p>tabWidget-&gt;setCornerWidget(widget, Qt::TopLeftCorner);<br />
str.indexOf(&quot;$(QTDIR)&quot;, Qt::CaseInsensitive);<br />
[/code]</p>
<p>当对枚举值进行或运算并作为某种标志（flag）时，传统的做法是把或运算的结果保存在int型的值中，但这不是类型安全的。Qt 4提供了一个模板类<code>QFlags</code>，其中的<code>T</code>是枚举类型。为了方便使用，Qt用<code>typedef</code>重新定义了<code>QFlag</code>类型，所以可以用<code>Qt::Alignment</code>代替<code>QFlags</code>。</p>
<p>习惯上，枚举类型命名用单数形式（因为它一次只能『持有』一个flag），而持有多个『flag』的类型用复数形式，例如：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
enum RectangleEdge { LeftEdge, RightEdge, &#8230; };<br />
typedef QFlags&lt;RectangleEdge&gt; RectangleEdges;<br />
[/code]</p>
<p>在某些情形下，持有多个『flag』的类型命名用单数形式。对于这种情况，持有的枚举类型名称要求是以<code>Flag</code>为后缀：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
enum AlignmentFlag { AlignLeft, AlignTop, &#8230; };<br />
typedef QFlags&lt;AlignmentFlag&gt; Alignment;<br />
[/code]</p>
<h2>6.4 函数和参数的命名</h2>
<p>函数命名的第一准则是可以从函数名看出来此函数是否有副作用。在Qt 3中，const函数<code>QString::simplifyWhiteSpace()</code>违反了此准则，因为它返回了一个<code>QString</code>而不是按名称暗示的那样，改变调用它的<code>QString</code>对象。在Qt 4中，此函数重命名为<code>QString::simplified()</code>。</p>
<p>虽然参数名不会出现在使用API的代码中，但是它们给程序员提供了重要信息。因为现代的IDE都会在写代码时显示参数名称，所以值得在头文件中给参数起一个恰当的名字并在文档中使用相同的名字。</p>
<h2>6.5 布尔类型的getter与setter方法的命名</h2>
<p>为<code>bool</code>属性的getter和setter方法命名总是很痛苦。getter应该叫做<code>checked()</code>还是<code>isChecked()</code>？<code>scrollBarsEnabled()</code>还是<code>areScrollBarEnabled()</code>？</p>
<p>Qt 4中，我们套用以下准则为getter命名：</p>
<ul>
<li>形容词以<code>is</code>为前缀，例子：
<ul>
<li><code>isChecked()</code></li>
<li><code>isDown()</code></li>
<li><code>isEmpty()</code></li>
<li><code>isMovingEnabled()</code></li>
</ul>
</li>
<li>然而，修饰名词的形容词没有前缀：
<ul>
<li><code>scrollBarsEnabled()</code>，而不是<code>areScrollBarsEnabled()</code></li>
</ul>
</li>
<li>动词没有前缀，也不使用第三人称(<code>-s</code>)：
<ul>
<li><code>acceptDrops()</code>，而不是<code>acceptsDrops()</code></li>
<li><code>allColumnsShowFocus()</code></li>
</ul>
</li>
<li>名词一般没有前缀：
<ul>
<li><code>autoCompletion()</code>，而不是<code>isAutoCompletion()</code></li>
<li><code>boundaryChecking()</code></li>
</ul>
</li>
<li>有的时候，没有前缀容易产生误导，这种情况下会加上<code>is</code>前缀：
<ul>
<li><code>isOpenGLAvailable()</code>，而不是<code>openGL()</code></li>
<li><code>isDialog()</code>，而不是<code>dialog()</code><br />
（一个叫做<code>dialog()</code>的函数，一般会被认为是返回<code>QDialog</code>。）</li>
</ul>
</li>
</ul>
<p>setter的名字由getter衍生，去掉了前缀后在前面加上了<code>set</code>；例如，<code>setDown()</code>与<code>setScrollBarsEnabled()</code>。</p>
<h1>7. 避免常见陷阱</h1>
<h2>7.1 简化的陷阱</h2>
<p>一个常见的误解是：实现需要写的代码越少，API就设计得越好。应该记住：代码只会写上几次，却要被反复阅读并理解。例如：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical, 0, &quot;volume&quot;);<br />
[/code]</p>
<p>这段代码比下面的读起来要难得多（甚至写起来也更难）：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
QSlider *slider = new QSlider(Qt::Vertical);<br />
slider-&gt;setRange(12, 18);<br />
slider-&gt;setPageStep(3);<br />
slider-&gt;setValue(13);<br />
slider-&gt;setObjectName(&quot;volume&quot;);<br />
[/code]</p>
<blockquote><p>【译注】：在有IDE的自动提示的支持下，后者写起来非常方便，而前者还需要看相应的文档。</p></blockquote>
<h2>7.2 布尔参数的陷阱</h2>
<p>布尔类型的参数总是带来无法阅读的代码。给现有的函数增加一个<code>bool</code>型的参数几乎永远是一种错误的行为。仍以Qt为例，<code>repaint()</code>有一个<code>bool</code>类型的可选参数用于指定背景是否被擦除。可以写出这样的代码：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
widget-&gt;repaint(false);<br />
[/code]</p>
<p>初学者很可能是这样理解的，『不要重新绘制！』，能有多少Qt用户真心知道下面3行是什么意思：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
widget-&gt;repaint();<br />
widget-&gt;repaint(true);<br />
widget-&gt;repaint(false);<br />
[/code]</p>
<p>更好的API设计应该是这样的：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
widget-&gt;repaint();<br />
widget-&gt;repaintWithoutErasing();<br />
[/code]</p>
<p>在Qt 4中，我们通过移除了重新绘制（repaint）而不擦除widget的能力来解决了此问题。Qt 4的双缓冲使这种特性被废弃。</p>
<p>还有更多的例子：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
widget-&gt;setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding, true);<br />
textEdit-&gt;insert(&quot;Where&#8217;s Waldo?&quot;, true, true, false);<br />
QRegExp rx(&quot;moc_***.c??&quot;, false, true);<br />
[/code]</p>
<p>一个明显的解决方案是<code>bool</code>类型改成枚举类型。我们在Qt 4的<code>QString</code>中就是这么做的。对比效果如下：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
str.replace(&quot;%USER%&quot;, user, false);               // Qt 3<br />
str.replace(&quot;%USER%&quot;, user, Qt::CaseInsensitive); // Qt 4<br />
[/code]</p>
<blockquote><p>【译注】：关于这个条目可以看看 CoolShell 这篇文章一些展开的讨论： <a href="https://coolshell.cn/articles/5444.html" rel="nofollow">千万不要把 BOOL 设计成函数参数</a>。</p></blockquote>
<h1>8. 案例研究</h1>
<h2>8.1 <code>QProgressBar</code></h2>
<p>为了展示上文各种准则的实际应用。我们来研究一下Qt 3中<code>QProgressBar</code>的API，并与Qt 4中对应的API作比较。在Qt 3中：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
class QProgressBar : public QWidget<br />
{<br />
    &#8230;<br />
public:<br />
    int totalSteps() const;<br />
    int progress() const;</p>
<p>    const QString &amp;progressString() const;<br />
    bool percentageVisible() const;<br />
    void setPercentageVisible(bool);</p>
<p>    void setCenterIndicator(bool on);<br />
    bool centerIndicator() const;</p>
<p>    void setIndicatorFollowsStyle(bool);<br />
    bool indicatorFollowsStyle() const;</p>
<p>public slots:<br />
    void reset();<br />
    virtual void setTotalSteps(int totalSteps);<br />
    virtual void setProgress(int progress);<br />
    void setProgress(int progress, int totalSteps);</p>
<p>protected:<br />
    virtual bool setIndicator(QString &amp;progressStr,<br />
                              int progress,<br />
                              int totalSteps);<br />
    &#8230;<br />
};<br />
[/code]</p>
<p>该API相当的复杂和不一致；例如，<code>reset()</code>、<code>setTotalSteps()</code>、<code>setProgress()</code>是紧密联系的，但方法的命名并没明确地表达出来。</p>
<p>改善此API的关键是抓住<code>QProgressBar</code>与Qt 4的<code>QAbstractSpinBox</code>及其子类<code>QSpinBox</code>、<code>QSlider</code>、<code>QDail</code>之间的相似性。怎么做？把<code>progress</code>、<code>totalSteps</code>替换为<code>minimum</code>、<code>maximum</code>和<code>value</code>。增加一个<code>valueChanged()</code>消息，再增加一个<code>setRange()</code>函数。</p>
<p>进一步可以观察到<code>progressString</code>、<code>percentage</code>与<code>indicator</code>其实是一回事，即是显示在进度条上的文本。通常这个文本是个百分比，但是可通过<code>setIndicator()</code>设置为任何内容。以下是新的API：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
virtual QString text() const;<br />
void setTextVisible(bool visible);<br />
bool isTextVisible() const;<br />
[/code]</p>
<p>默认情况下，显示文本是百分比指示器（percentage indicator），通过重写<code>text()</code>方法来定制行为。</p>
<p>Qt 3的<code>setCenterIndicator()</code>与<code>setIndicatorFollowsStyle()</code>是两个影响对齐方式的函数。他们可被一个<code>setAlignment()</code>函数代替：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
void setAlignment(Qt::Alignment alignment);<br />
[/code]</p>
<p>如果开发者未调用<code>setAlignment()</code>，那么对齐方式由风格决定。对于基于<code>Motif</code>的风格，文字内容在中间显示；对于其他风格，在右侧显示。</p>
<p>下面是改善后的<code>QProgressBar API</code>:</p>
<p>[code language=&#8221;cpp&#8221;]<br />
class QProgressBar : public QWidget<br />
{<br />
    &#8230;<br />
public:<br />
    void setMinimum(int minimum);<br />
    int minimum() const;<br />
    void setMaximum(int maximum);<br />
    int maximum() const;<br />
    void setRange(int minimum, int maximum);<br />
    int value() const;</p>
<p>    virtual QString text() const;<br />
    void setTextVisible(bool visible);<br />
    bool isTextVisible() const;<br />
    Qt::Alignment alignment() const;<br />
    void setAlignment(Qt::Alignment alignment);</p>
<p>public slots:<br />
    void reset();<br />
    void setValue(int value);</p>
<p>signals:<br />
    void valueChanged(int value);<br />
    &#8230;<br />
};<br />
[/code]</p>
<h2>8.2 <code>QAbstractPrintDialog</code> &amp; <code>QAbstractPageSizeDialog</code></h2>
<p>Qt 4.0有2个幽灵类<code>QAbstractPrintDialog</code>和<code>QAbstractPageSizeDialog</code>，作为 <code>QPrintDialog</code>和<code>QPageSizeDialog</code>类的父类。这2个类完全没有用，因为Qt的API没有是<code>QAbstractPrint-</code>或是<code>-PageSizeDialog</code>指针作为参数并执行操作。通过篡改qdoc（Qt文档），我们虽然把这2个类隐藏起来了，却成了无用抽象类的典型案例。</p>
<p>这不是说，<strong><em>好</em></strong> 的抽象是错的，<code>QPrintDialog</code>应该是需要有个工厂或是其它改变的机制 —— 证据就是它声明中的<code>#ifdef QTOPIA_PRINTDIALOG</code>。</p>
<h2>8.3 <code>QAbstractItemModel</code></h2>
<p>关于模型/视图（model/view）问题的细节在相应的文档中已经说明得很好了，但作为一个重要的总结这里还需要强调一下：抽象类不应该仅是所有可能子类的并集（union）。这样『合并所有』的父类几乎不可能是一个好的方案。<code>QAbstractItemModel</code>就犯了这个错误 —— 它实际上就是个<code>QTreeOfTablesModel</code>，结果导致了错综复杂（complicated）的API，而这样的API要让 <strong><em>所有本来设计还不错的子类</em></strong> 去继承。</p>
<p>仅仅增加抽象是不会自动就把API变得更好的。</p>
<h2>8.4 <code>QLayoutIterator</code> &amp; <code>QGLayoutIterator</code></h2>
<p>在Qt 3，创建自定义的布局类需要同时继承<code>QLayout</code>和<code>QGLayoutIterator</code>（命名中的<code>G</code>是指Generic（通用））。<code>QGLayoutIterator</code>子类的实例指针会包装成<code>QLayoutIterator</code>，这样用户可以像和其它的迭代器（iterator）类一样的方式来使用。通过<code>QLayoutIterator</code>可以写出下面这样的代码：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
QLayoutIterator it = layout()-&gt;iterator();<br />
QLayoutItem **child;<br />
while ((child = it.current()) != 0) {<br />
    if (child-&gt;widget() == myWidget) {<br />
        it.takeCurrent();<br />
        return;<br />
    }<br />
    ++it;<br />
}<br />
[/code]</p>
<p>在Qt 4，我们干掉了<code>QGLayoutIterator</code>类（以及用于盒子布局和格子布局的内部子类），转而是让<code>QLayout</code>的子类重写<code>itemAt()</code>、<code>takeAt()</code>和<code>count()</code>。</p>
<h2>8.5 <code>QImageSink</code></h2>
<p>Qt 3有一整套类用来把完成增量加载的图片传递给一个动画 —— <code>QImageSource</code>/<code>Sink</code>/<code>QASyncIO</code>/<code>QASyncImageIO</code>。由于这些类之前只是用于启用动画的<code>QLabel</code>，完全过度设计了（overkill）。</p>
<p>从中得到的教训就是：对于那些未来可能的还不明朗的需求，不要过早地增加抽象设计。当需求真的出现时，比起一个复杂的系统，在简单的系统新增需求要容易得多。<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" id="wp_rp_first"><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/5444.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/14.jpg" alt="千万不要把 bool 设计成函数参数" width="150" height="150" /></a><a href="https://coolshell.cn/articles/5444.html" class="wp_rp_title">千万不要把 bool 设计成函数参数</a></li><li ><a href="https://coolshell.cn/articles/4758.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/30.jpg" alt="如何写出无法维护的代码" width="150" height="150" /></a><a href="https://coolshell.cn/articles/4758.html" class="wp_rp_title">如何写出无法维护的代码</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/17680.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/02/gitlab-600-150x150.jpg" alt="从Gitlab误删除数据库想到的" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17680.html" class="wp_rp_title">从Gitlab误删除数据库想到的</a></li><li ><a href="https://coolshell.cn/articles/17459.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2016/08/HighAvailability-BK-150x150.png" alt="关于高可用的系统" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17459.html" class="wp_rp_title">关于高可用的系统</a></li><li ><a href="https://coolshell.cn/articles/12052.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/29.jpg" alt="Leetcode 编程训练" width="150" height="150" /></a><a href="https://coolshell.cn/articles/12052.html" class="wp_rp_title">Leetcode 编程训练</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/18024.html">API设计原则 – Qt官网的设计实践总结</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/18024.html/feed</wfw:commentRss>
			<slash:comments>26</slash:comments>
		
		
			</item>
		<item>
		<title>API设计：用流畅接口构造内部DSL</title>
		<link>https://coolshell.cn/articles/5709.html</link>
					<comments>https://coolshell.cn/articles/5709.html#comments</comments>
		
		<dc:creator><![CDATA[Todd]]></dc:creator>
		<pubDate>Mon, 31 Oct 2011 00:28:47 +0000</pubDate>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Ruby]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=5709</guid>

					<description><![CDATA[<p>感谢@weidagang （Todd）向酷壳投递本文。 程序设计语言的抽象机制包含了两个最基本的方面：一是语言关注的基本元素/语义；另一个是从基本元素/语义到复...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/5709.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/5709.html">API设计：用流畅接口构造内部DSL</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><strong>感谢<a href="http://weibo.com/n/weidagang">@weidagang</a> （Todd）向酷壳投递本文。</strong></p>
<p>程序设计语言的抽象机制包含了两个最基本的方面：一是语言关注的基本元素/语义；另一个是从基本元素/语义到复合元素/语义的构造规则。在C、C++、Java、C#、Python等通用语言中，语言的基本元素/语义往往离问题域较远，通过API库的形式进行层层抽象是降低问题难度最常用的方法。比如，在C语言中最常见的方式是提供函数库来封装复杂逻辑，方便外部调用。</p>
<p>不过普通的API设计方法存在一种天然的陷阱，那就是不管怎样封装，大过程虽然比小过程抽象层次更高，但本质上还是过程，受到过程语义的制约。也就是说，通过基本元素/语义构造更高级抽象元素/语义的时候，语言的构造规则很大程度上限制了抽象的维度，我们很难跳出这个维度去，甚至可能根本意识不到这个限制。而SQL、HTML、CSS、make等DSL（领域特定语言）的抽象维度是为特定领域量身定做的，从这些抽象角度看问题往往最为简单，所以DSL在解决其特定领域的问题时比通用程序设计语言更加方便。通常，SQL等非通用语言被称为外部DSL（External DSL）；在通用语言中，我们其实也可以在一定程度上突破语言构造规则的抽象维度限制，定义内部DSL（Internal DSL）。</p>
<p>本文将介绍一种被称为流畅接口（Fluent Interface）的内部DSL设计方法。Wikipedia上<a title="Fluent Interface" href="http://en.wikipedia.org/wiki/Fluent_interface">Fluent Interface</a>的定义是：</p>
<blockquote><p>A fluent interface (as first coined by Eric Evans and Martin Fowler) is an implementation of an object oriented API that aims to provide for more readable code. A fluent interface is normally implemented by using method chaining to relay the instruction context of a subsequent call (but a fluent interface entails more than just method chaining).</p></blockquote>
<div>
<p>下面将分4个部分来逐步说明流畅接口在构造内部DSL中的典型应用。</p>
</div>
<h4><strong>1. 基本语义抽象</strong></h4>
<p>如果要输出0..4这5个数，我们一般会首先想到类似这样的代码：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
//Java
for (int i = 0; i &lt; 5; ++i) {
    system.out.println(i);
}</pre>
<p><span id="more-5709"></span></p>
<p>而Ruby虽然也支持类似的for循环，但最简单的是下面这样的实现：</p>
<pre data-enlighter-language="ruby" class="EnlighterJSRAW">
//Ruby
5.times {|i| puts i}</pre>
<p>Ruby中一切皆对象，5是Fixnum类的实例，times是Fixnum的一个方法，它接受一个block参数。相比for循环实现，Ruby的times方式更简洁，可读性更强，但熟悉OOP的朋友可能会有疑问，times是否应该作为整型类的方法呢？在OOP中，方法调用通常代表了向对象发送消息，改变或查询对象的状态，times方法显然不是对整型对象状态的查询和修改。如果你是Ruby的设计者，你会把times方法放入Fixnum类吗？如果答案是否定的，那么Ruby的这种设计本质上代表了什么呢？实际上，这里的times虽然只是一个普通的类方法，但它的目的却与普通意义上的类方法不同，它的语义实际上类似于for循环这样的语言基本语义，可以被视为一种自定义的基本语义。times的语义从一定程度上跳出了类方法的框框，向问题域迈进了一步！</p>
<p>另一个例子来自Eric Evans的“用两个时间点构造一个时间段对象”，普通设计：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
//Java
TimePoint fiveOClock, sixOClock;
TimeInterval meetingTime = new TimeInterval(fiveOClock, sixOClock);</pre>
<p>另一种Evans的设计是这样：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
//Java
TimeInterval meetingTime = fiveOClock.until(sixOClock);</pre>
<p>按传统OO设计，until方法本不应出现在TimePoint类中，这里TimePoint类的until方法同样代表了一种自定义的基本语义，使得表达时间域的问题更加自然。</p>
<p>虽然上面的两个简单例子和普通设计相比看不出太大的优势，但它却为我们理解流畅接口打下了基础。重要的是应该体会到它们从一定程度上跳出了语言基本抽象机制的束缚，我们不应该再用类职责划分、迪米特法则（Law of Demeter）等OO设计原则来看待它们。</p>
<h4><strong>2. 管道抽象</strong></h4>
<p>在Shell中，我们可以通过管道将一系列的小命令组合在一起实现复杂的功能。管道中流动的是单一类型的文本流，计算过程就是从输入流到输出流的变换过程，每个命令是对文本流的一次变换作用，通过管道将作用叠加起来。在Shell中，很多时候我们只需要一句话就能完成log统计这样的中小规模问题。和其他抽象机制相比，管道的优美在于无嵌套。比如下面这段C程序，由于嵌套层次较深，不容易一下子理解清楚：</p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">
//C
min(max(min(max(a,b),c),d),e)
</pre>
<p>而用管道来表达同样的功能则清晰得多：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
#!/bin/bash
max a b | min c | max d | min e
</pre>
<p>我们很容易理解这段程序表达的意思是：先求a, b的最大值；再把结果和c取最小值；再把结果和d求最大值；再把结果和e求最小值。</p>
<p>jQuery的链式调用设计也具有管道的风格，方法链上流动的是同一类型的jQuery对象，每一步方法调用是对对象的一次作用，整个方法链将各个方法的作用叠加起来。</p>
<pre data-enlighter-language="js" class="EnlighterJSRAW">
//Javascript
$(&#039;li&#039;).filter(&#039;:event&#039;).css(&#039;background-color&#039;, &#039;red&#039;);
</pre>
<h4>3. 层次结构抽象</h4>
<p>除了管道这种“线性”结构外，流畅接口还可用于构造层次结构抽象。比如，用Javascript动态创建创建下面的HTML片段：</p>
<pre data-enlighter-language="html" class="EnlighterJSRAW">
&lt;div id=&quot;’product_123’&quot; class=&quot;’product’&quot;&gt;
&lt;img src=&quot;’preview_123.jpg’&quot; alt=&quot;&quot; /&gt;
&lt;ul&gt;
	&lt;li&gt;Name: iPad2 32G&lt;/li&gt;
	&lt;li&gt;Price: 3600&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

</pre>
<p>若采用Javascript的DOM API：</p>
<pre data-enlighter-language="js" class="EnlighterJSRAW">
//Javascript
var div = document.createElement(&#039;div&#039;);
div.setAttribute(‘id’, ‘product_123’);
div.setAttribute(‘class’, ‘product’);

var img = document.createElement(&#039;img&#039;);
img.setAttribute(‘src’, ‘preview_123.jpg’);
div.appendChild(img);

var ul = document.createElement(&#039;ul&#039;);
var li1 = document.createElement(&#039;li&#039;);
var txt1 = document.createTextNode(&quot;Name: iPad2 32G&quot;);
li1.appendChild(txt1);
…
div.appendChild(ul);</pre>
<p>而下面流畅接口API则要有表现力得多：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
//Javascript
var obj =
$.div({id:’product_123’, class:’product’})
    .img({src:’preview_123.jpg’})
    .ul()
        .li().text(‘Name: iPad2 32G’)._li()
        .li().text(‘Price: 3600’)._li()
    ._ul()
 ._div();</pre>
<div>和Javascript的标准DOM API相比，上面的API设计不再局限于孤立地看待某一个方法，而是考虑了它们在解决问题时的组合使用，所以代码的表现形式特别贴近问题的本质。这样的代码是自解释的（self-explanatory）在可读性方面要明显胜于DOM API，这相当于定义了一种类似于HTML的内部DSL，它拥有自己的语义和语法。需要特别注意的是，上面的层次结构抽象和管道抽象有着本质的不同，管道抽象的方法链上通常是同一对象的连续传递，而层次抽象中方法链上的对象却在随着层次的变化而变化。此为，我们可以把业务规则也表达在流畅接口中，比如上面的例子中，body()不能包含在div()返回的对象中，div().body()将抛出&#8221;body方法不存在”异常。</div>
<h4><strong>4. 异步抽象</strong></h4>
<div>流畅接口不仅可以构造复杂的层次抽象，还可以用于构造异步抽象。在基于回调机制的异步模式中，多个异步调用的同步和嵌套问题是使用异步的难点所在。有时一个稍复杂的调用和同步关系会导致代码充满了复杂的同步检查和层层回调，难以理解和维护。这个问题从本质上讲和上面HTML的例子一样，是由于多数通用语言并未把异步作为基本元素/语义，许多异步实现模式是向语言的妥协。针对这个问题，我用Javascript编写了一个基于流畅接口的异步DSL，示例代码如下：</div>
<div>[javascript]<br />
//Javascript<br />
$.begin()<br />
    .async(newTask(&#8216;task1&#8217;), &#8216;task1&#8217;)<br />
    .async(newTask(&#8216;task2&#8217;), &#8216;task2&#8217;)<br />
    .async(newTask(&#8216;task3&#8217;), &#8216;task3&#8217;)<br />
.when()<br />
    .each_done(function(name, result) {<br />
        console.log(name + &#8216;: &#8216; + result);})<br />
    .all_done(function(){ console.log(&#8216;good, all completed&#8217;); })<br />
    .timeout(function(){<br />
        console.log(&#8216;timeout!!&#8217;);<br />
        $.begin()<br />
            .async(newTask(&#8216;task4&#8217;), &#8216;task4&#8217;)<br />
        .when()<br />
            .each_done(function(name, result) {<br />
                console.log(name + &#8216;: &#8216; + result); })<br />
        .end();}<br />
        , 3000)<br />
.end();[/javascript]</p>
</div>
<div>上面的代码只是一句Javascript调用，但从另一个角度看它却像一段描述异步调用的DSL程序。它通过流畅接口定义了begin when end的语法结构，begin后面跟的是启动异步调用的代码；when后面是异步结果处理，可以选择each_done, all_done, timeout中的一种或多种。而begin when end结构本身是可以嵌套的，比如上面的代码在timeout处理分支中就包含了另一个begin when end结构。通过这个DSL，我们可以比基于回调的方式更好地表达异步调用的同步和嵌套关系。</div>
<p>上面介绍了用流畅接口构造的4种典型抽象，出此之外还有很多其他的抽象和应用场合，比如：不少单元测试框架就通过流畅接口定义了单元测试的DSL。虽然上面的例子以Javascript等动态语言居多，但其实流畅接口所依赖的语法基础并不苛刻，即使在Java这样的静态语言中，同样可以轻松地使用。流畅接口不同于传统的API设计，理解和使用流畅接口关键是要突破语言抽象机制带来的定势思维，根据问题域选取适当的抽象维度，利用语言的基本语法构造领域特定的语义和语法。</p>
<p><strong>参考</strong></p>
<ul>
<li><a title="Wikipedia: Fluent Interface" href="http://en.wikipedia.org/wiki/Fluent_interface">Wikipedia: Fluent Interface</a></li>
<li><a title="Martin Fowler: Fluent Interface" href="http://www.martinfowler.com/bliki/FluentInterface.html">Martin Fowler: Fluent Interface</a></li>
<li><a title="jQuery is DSL" href="http://www.cnblogs.com/cathsfz/archive/2009/08/10/1543266.html">jQuery is DSL</a></li>
<li><a title="An Approach to Internal Domain-Specific Languages in Java" href="http://www.infoq.com/articles/internal-dsls-java">An Approach to Internal Domain-Specific Languages in Java</a></li>
</ul>
<p><!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/10337.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/24.jpg" alt="数据即代码：元驱动编程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/10337.html" class="wp_rp_title">数据即代码：元驱动编程</a></li><li ><a href="https://coolshell.cn/articles/5202.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/14.jpg" alt="对象的消息模型" width="150" height="150" /></a><a href="https://coolshell.cn/articles/5202.html" class="wp_rp_title">对象的消息模型</a></li><li ><a href="https://coolshell.cn/articles/3437.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2010/12/ediff-small-150x150.png" alt="一些杂项资源" width="150" height="150" /></a><a href="https://coolshell.cn/articles/3437.html" class="wp_rp_title">一些杂项资源</a></li><li ><a href="https://coolshell.cn/articles/2053.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/20.jpg" alt="最为奇怪的程序语言的特性" width="150" height="150" /></a><a href="https://coolshell.cn/articles/2053.html" class="wp_rp_title">最为奇怪的程序语言的特性</a></li><li ><a href="https://coolshell.cn/articles/1992.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2009/12/language-fanboys-150x150.jpg" alt="程序员眼中的编程语言" width="150" height="150" /></a><a href="https://coolshell.cn/articles/1992.html" class="wp_rp_title">程序员眼中的编程语言</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/5709.html">API设计：用流畅接口构造内部DSL</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/5709.html/feed</wfw:commentRss>
			<slash:comments>32</slash:comments>
		
		
			</item>
		<item>
		<title>千万不要把 bool 设计成函数参数</title>
		<link>https://coolshell.cn/articles/5444.html</link>
					<comments>https://coolshell.cn/articles/5444.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Thu, 08 Sep 2011 07:35:18 +0000</pubDate>
				<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[Bool]]></category>
		<category><![CDATA[Coding]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[程序员]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=5444</guid>

					<description><![CDATA[<p>我们有很多Coding Style 或 代码规范。但这一条可能会经常被我们所遗忘，就是我们经常会在函数的参数里使用bool参数，这会大大地降低代码的可读性。不信...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/5444.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/5444.html">千万不要把 bool 设计成函数参数</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script>我们有很多Coding Style 或 代码规范。但这一条可能会经常被我们所遗忘，就是我们经常会在函数的参数里使用bool参数，这会大大地降低代码的可读性。不信？我们先来看看下面的代码。</p>
<p>当你读到下面的代码，你会觉得这个代码是什么意思？</p>
<p><code data-enlighter-language="c" class="EnlighterJSRAW">widget-&gt;repaint(false);</code></p>
<p>是不要repaint吗？还是别的什么意思？看了文档后，我们才知道这个参数是immediate， 也就是说，false代表不立即重画，true代码立即重画。</p>
<p>Windows API中也有这样一个函数：InvalidateRect，当你看到下面的代码，你会觉得是什么意思？</p>
<p><code data-enlighter-language="c" class="EnlighterJSRAW">InvalidateRect(hwnd, lpRect,  false);</code></p>
<p>我们先不说InvalidateRect这个函数名取得有多糟糕，我们先说一下那个false参数？invalidate意为“让XXX无效”，false是什么意思？双重否定？是肯定的意思？如果你看到这样的代码，你会相当的费解的。于是，你要去看一下文档，或是InvalidateRect的函数定义，你会看到那个参数是 <strong>BOOL</strong><em> bErase</em>，意思是，是否要重画背景。</p>
<p>这样的事情有很多，再看下面的代码，想把str中的&#8221;%USER%&#8221;替换成真实的用户名：</p>
<p><code data-enlighter-language="c" class="EnlighterJSRAW">str.replace(&quot;%USER%&quot;, user, false);   // Qt 3</code></p>
<p>TNND，那个false是什么意思？不替换吗？还是别的什么意思，看了文档才知道，false代码大小写不敏感的替换。</p>
<p>其实，如果你使用枚举变量/常量，而不是bool变量，你会让你的代码更易读，如：</p>
<p><span id="more-5444"></span></p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">widget-&gt;repaint(PAINT::immediate);
widget-&gt;repaint(PAINT::deffer);

InvalidateRect(hwnd, lpRect,  !RepantBackground);

str.replace(&quot;%USER%&quot;, user, Qt::CaseInsensitive); // Qt 4</pre>
<p>如果对这个事不以为然的话，我们再来看一些别的示例，你不妨猜猜看看下面的代码：</p>
<p><code data-enlighter-language="c" class="EnlighterJSRAW">component.setCentered(true, false);</code></p>
<p>这什么玩意儿啊？看了文档你才知道，这原来是 setCentered(centered, autoUpdate);</p>
<p><code data-enlighter-language="c" class="EnlighterJSRAW">new Textbox(300, 100, false, true);</code></p>
<p>这又是什么啊？看了文档才知道，这是创建一个文本框，第三个参数是是否要滚动条，第四个是是否要自动换行。TNND。</p>
<p>上面的情况还不算最差，看看下面的双重否定。</p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">component.setDisabled(false);
filter.setCaseInsensitive(false)</pre>
<p>再来一个，如果你读到下面的代码，相信你会和我一样，要么石化了，要么凌乱了。</p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">event.initKeyEvent(&quot;keypress&quot;, true, true, null, null,
                    false, false, false, false, 9, 0); </pre>
<p>看完这篇文章，我希望你再也不要把bool为作为函数参数了。除非两个原因：</p>
<ol>
<li>你100%确认不会带来阅读上的问题，比如Java的 setVisible (bool).</li>
<li>你100%确认你想去<a title="如何写出无法维护的代码" href="https://coolshell.cn/articles/4758.html" target="_blank">写出无法维护很难阅读的代码</a>。</li>
</ol>
<p>【更新2011/9/8】当然，别的参数也会有一样的问题，比如：<code>new Textbox(300, 100, false, true);</code>中的300 和 100，不知道是坐标还是长宽，只不过，一般长度或坐标这样的参数都不会被hard code，都会有变量名，而bool这种参数经常性地被传成true 和 false。 bool参数表现得更为明显一些罢了。</p>
<p><span style="color: #cc0000;">所以，程序中不要出现magic number，true/false 也是一种 magic number。但是，我想告诉大家，从API设计的角度来说，你无法强制调用者用常量来取代true/false，定义成枚举类型是最好的选择</span>。</p>
<p>最后，如果你想设计一个好的API，强烈推荐你读一下Nokia的Qt的《<a href="http://qt-project.org/wiki/API-Design-Principles" target="_blank">API Design Principles</a>》，本文就是其中的“<a href="http://developer.qt.nokia.com/wiki/API_Design_Principles#e7794937cba47d5e9c54d50a6a32328b" target="_blank">Boolean Trap</a>”。</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/api-design-300x278-2-150x150.jpg" alt="API设计原则 &#8211; Qt官网的设计实践总结" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_title">API设计原则 &#8211; Qt官网的设计实践总结</a></li><li ><a href="https://coolshell.cn/articles/17680.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/02/gitlab-600-150x150.jpg" alt="从Gitlab误删除数据库想到的" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17680.html" class="wp_rp_title">从Gitlab误删除数据库想到的</a></li><li ><a href="https://coolshell.cn/articles/17459.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2016/08/HighAvailability-BK-150x150.png" alt="关于高可用的系统" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17459.html" class="wp_rp_title">关于高可用的系统</a></li><li ><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/07/inverted-bookshelf_thumb-150x150.jpg" alt="IoC/DIP其实是一种管理思想" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_title">IoC/DIP其实是一种管理思想</a></li><li ><a href="https://coolshell.cn/articles/6775.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/24.jpg" alt="Bret Victor &#8211; Inventing on Principle" width="150" height="150" /></a><a href="https://coolshell.cn/articles/6775.html" class="wp_rp_title">Bret Victor &#8211; Inventing on Principle</a></li><li ><a href="https://coolshell.cn/articles/5686.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/15.jpg" alt="多些时间能少写些代码" width="150" height="150" /></a><a href="https://coolshell.cn/articles/5686.html" class="wp_rp_title">多些时间能少写些代码</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/5444.html">千万不要把 bool 设计成函数参数</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/5444.html/feed</wfw:commentRss>
			<slash:comments>94</slash:comments>
		
		
			</item>
		<item>
		<title>Google未公开API：转MAC地址为经纬度</title>
		<link>https://coolshell.cn/articles/3089.html</link>
					<comments>https://coolshell.cn/articles/3089.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Sat, 09 Oct 2010 07:28:13 +0000</pubDate>
				<category><![CDATA[Web开发]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[GPS]]></category>
		<category><![CDATA[XSS]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=3089</guid>

					<description><![CDATA[<p>这里有一个POC（Proof of Concept）可以通过你Web浏览器后面的路由器XSS攻击得到一个准确的GPS坐标。注意：路由器和Web浏览器以及IP地址...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/3089.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/3089.html">Google未公开API：转MAC地址为经纬度</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script>这里有一个POC（Proof of Concept）可以通过你Web浏览器后面的路由器XSS攻击得到一个准确的GPS坐标。注意：路由器和Web浏览器以及IP地址并不包含任和地理信息。其方法是使用了一个Google未公开的API。大约方法如下：</p>
<ol>
<li>访问一个网页，这个网页隐藏了一个基于你WiFi路由器的XSS（ 参见： <a href="http://samy.pl/vzwfios/" target="_blank">XSS  Verizon FiOS router</a>）</li>
<li>通过这个XSS 可以获得路由器的MAC 地址。</li>
<li>然后通过 Google Location Services我们可以把这个MAC地址映射到GPS坐标。Googel的这个服务是基于HTTP的服务。这并不是一个Google正式发布的API，而是通过 <a href="http://www.mozilla.com/en-US/firefox/geolocation/" target="_blank">Firefox&#8217;s Location-Aware Browsing</a> 发现的。</li>
</ol>
<p>演示地点在这里：<a href="http://samy.pl/mapxss/" target="_blank">http://samy.pl/mapxss/</a></p>
<p>我试了一下，无论无线和有线都可以准确定位我的位置。很强大，你也试试看。<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/22398.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2023/02/chatgpt-150x150.jpg" alt="我看ChatGPT: 为啥谷歌掉了千亿美金" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22398.html" class="wp_rp_title">我看ChatGPT: 为啥谷歌掉了千亿美金</a></li><li ><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/api-design-300x278-2-150x150.jpg" alt="API设计原则 &#8211; Qt官网的设计实践总结" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_title">API设计原则 &#8211; Qt官网的设计实践总结</a></li><li ><a href="https://coolshell.cn/articles/12136.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/11/inbox2-640x264-150x150.jpg" alt="Google Inbox如何跨平台重用代码？" width="150" height="150" /></a><a href="https://coolshell.cn/articles/12136.html" class="wp_rp_title">Google Inbox如何跨平台重用代码？</a></li><li ><a href="https://coolshell.cn/articles/9508.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/04/Google-Person-Finder-150x150.png" alt="PFIF网上寻人协议" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9508.html" class="wp_rp_title">PFIF网上寻人协议</a></li><li ><a href="https://coolshell.cn/articles/8711.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/12/200906020837401710-150x150.jpg" alt="程序员疫苗：代码注入" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8711.html" class="wp_rp_title">程序员疫苗：代码注入</a></li><li ><a href="https://coolshell.cn/articles/5815.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/2.jpg" alt="来信， 创业 和 移动互联网" width="150" height="150" /></a><a href="https://coolshell.cn/articles/5815.html" class="wp_rp_title">来信， 创业 和 移动互联网</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/3089.html">Google未公开API：转MAC地址为经纬度</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/3089.html/feed</wfw:commentRss>
			<slash:comments>13</slash:comments>
		
		
			</item>
	</channel>
</rss>
