Browsed by
分类:技术读物

API设计原则

API设计原则

原文链接:API Design Principles – QT Wiki
基于Gary的影响力上 Gary Gao 的译文稿:C++API设计指导

译序

Qt的设计水准在业界很有口碑,一致、易于掌握和强大的APIQt最著名的优点之一。此文既是Qt官网上的API设计指导准则,也是QtAPI设计上的实践总结。虽然Qt用的是C++,但其中设计原则和思考是具有普适性的(如果你对C++还不精通,可以忽略与C++强相关或是过于细节的部分,仍然可以学习或梳理关于API设计最有价值的内容)。整个篇幅中有很多示例,是关于API设计一篇难得的好文章。

api design

需要注意的是,这篇Wiki有一些内容并不完整,所以,可能会有一些阅读上的问题,我们对此做了一些相关的注释。

感谢酷壳博主、C++大拿 左耳朵耗子的全文审校,并对难点和注意的地方给出贴心的说明和译注。

PS:翻译中肯定会有不足和不对之处,欢迎评论&交流;另译文源码在GitHub的这个仓库中,可以提交Issue/Fork后提交代码来建议/指正。

API设计原则

一致、易于掌握和强大的APIQt最著名的优点之一。此文总结了我们在设计Qt风格API的过程中所积累的诀窍(know-how)。其中许多是通用准则;而其他的则更偏向于约定,遵循这些约定主要是为了与已有的API保持一致。

虽然这些准则主要用于对外的APIpublic API),但在设计对内的APIprivate API)时也推荐遵循相同的技巧(techniques),作为开发者之间协作的礼仪(courtesy)。

如有兴趣也可以读一下 Jasmin Blanchette 的Little Manual of API Design (PDF) 或是本文的前身 Matthias Ettrich 的Designing Qt-Style C++ APIs

1. 好API的6个特质

API之于程序员就如同图形界面之于普通用户(end-user)。API中的『P』实际上指的是『程序员』(Programmer),而不是『程序』(Program),强调的是API是给程序员使用的这一事实。

在第13期Qt季刊Matthias 的关于API设计的文章中提出了观点:API应该极简(minimal)且完备(complete)、语义清晰简单(have clear and simple semantics)、符合直觉(be intuitive)、易于记忆(be easy to memorize)和引导API使用者写出可读代码(lead to readable code)。

1.1 极简

极简的API是指每个classpublic成员尽可能少,publicclass也尽可能少。这样的API更易理解、记忆、调试和变更。

1.2 完备

完备的API是指期望有的功能都包含了。这点会和保持API极简有些冲突。如果一个成员函数放在错误的类中,那么这个函数的潜在用户就会找不到,这也是违反完备性的。

1.3 语义清晰简单

就像其他的设计一样,我们应该遵守最少意外原则(the principle of least surprise)。好的API应该可以让常见的事完成的更简单,并有可以完成不常见的事的可能性,但是却不会关注于那些不常见的事。解决的是具体问题;当没有需求时不要过度通用化解决方案。(举个例子,在Qt 3中,QMimeSourceFactory不应命名成QImageLoader并有不一样的API。)

1.4 符合直觉

就像计算机里的其他事物一样,API应该符合直观。对于什么是符合直觉的什么不符合,不同经验和背景的人对是否符合直觉有不同的看法。API符合直观的测试方法:经验不很丰富的用户不用阅读API文档就能搞懂API,而且程序员不用了解API就能看明白使用API的代码。

1.5 易于记忆

为使API易于记忆,API的命名约定应该具有一致性和精确性。使用易于识别的模式和概念,并且避免用缩写。

1.6 引导API使用者写出可读代码

代码只写一次,却要多次的阅读(还有调试和修改)。写出可读性好的代码有时候要花费更多的时间,但对于产品的整个生命周期来说是节省了时间的。

最后,要记住的是,不同的用户会使用API的不同部分。尽管简单使用单个Qt类的实例应该符合直觉,但如果是要继承一个类,让用户事先看好文档是个合理的要求。

2. 静态多态

相似的类应该有相似的API。在继承(inheritance)合适时可以用继承达到这个效果,即运行时多态。然而多态也发生在设计阶段。例如,如果你用QProgressBar替换QSlider,或是用QString替换QByteArray,你会发现API的相似性使的替换很容易。这即是所谓的『静态多态』(static polymorphism)。

静态多态也使记忆API和编程模式更加容易。因此,一组相关的类有相似的API有时候比每个类都有各自的一套API更好。

一般来说,在Qt中,如果没有足够的理由要使用继承,我们更倾向于用静态多态。这样可以减少Qt public类的个数,也使刚学习Qt的用户在翻看文档时更有方向感。

2.1 好的案例

QDialogButtonBoxQMessageBox,在处理按钮(addButton()setStandardButtons()等等)上有相似的API,不需要继承某个QAbstractButtonBox类。

2.2 差的案例

QTcpSocketQUdpSocket都继承了QAbstractSocket,这两个类的交互行为的模式(mode of interaction)非常不同。似乎没有什么人以通用和有意义的方式用过QAbstractSocket指针(或者  以通用和有意义的方式使用QAbstractSocket指针)。

2.3 值得斟酌的案例

QBoxLayoutQHBoxLayoutQVBoxLayout的父类。好处:可以在工具栏上使用QBoxLayout,调用setOrientation()使其变为水平/垂直。坏处:要多一个类,并且有可能导致用户写出这样没什么意义的代码,((QBoxLayout *)hbox)->setOrientation(Qt::Vertical)

3. 基于属性的API

新的Qt类倾向于用『基于属性(property)的API』,例如:

QTimer timer;
timer.setInterval(1000);
timer.setSingleShot(true);
timer.start();

这里的 属性 是指任何的概念特征(conceptual attribute),是对象状态的一部分 —— 无论它是不是Q_PROPERTY。在说得通的情况下,用户应该可以以任何顺序设置属性,也就是说,属性之间应该是正交的(orthogonal)。例如,上面的代码可以写成:

QTimer timer;
timer.setSingleShot(true);
timer.setInterval(1000);
timer.start();

【译注】:正交性是指改变某个特性而不会影响到其他的特性。《程序员修炼之道》中讲了关于正交性的一个直升飞机坠毁的例子,讲得深入浅出很有画面感。

为了方便,也写成:

timer.start(1000);

类似地,对于QRegExp会是这样的代码:

QRegExp regExp;
regExp.setCaseSensitive(Qt::CaseInsensitive);
regExp.setPattern(".");
regExp.setPatternSyntax(Qt::WildcardSyntax);

为实现这种类型的API,需要借助底层对象的懒创建。例如,对于QRegExp的例子,在不知道模式语法(pattern syntax)的情况下,在setPattern()中去解释"."就为时过早了。

属性之间常常有关联的;在这种情况下,我们必须小心处理。思考下面的问题:当前的风格(style)提供了『默认的图标尺寸』属性 vs. QToolButton的『iconSize』属性:

toolButton->setStyle(otherStyle);
toolButton->iconSize();    // returns the default for otherStyle
toolButton->setIconSize(QSize(52, 52));
toolButton->iconSize();    // returns (52, 52)
toolButton->setStyle(yetAnotherStyle);
toolButton->iconSize();    // returns (52, 52)

提醒一下,一旦设置了iconSize,设置就会一直保持,即使改变当前的风格。这 很好。但有的时候需要能重置属性。有两种方法:

  1. 传入一个特殊值(如QSize()-1或者Qt::Alignment(0))来表示『重置』
  2. 提供一个明确的重置方法,如resetFoo()unsetFoo()

对于iconSize,使用QSize()(比如 QSize(–1, -1))来表示『重置』就够用了。

在某些情况下,getter方法返回的结果与所设置的值不同。例如,虽然调用了widget->setEnabled(true),但如果它的父widget处于disabled状态,那么widget->isEnabled()仍然返回的是false。这样是OK的,因为一般来说就是我们想要的检查结果(父widget处于disabled状态,里面的子widget也应该变为灰的不响应用户操作,就好像子widget自身处于disabled状态一样;与此同时,因为子widget记得在自己的内心深处是enabled状态的,只是一直等待着它的父widget变为enabled)。当然诸如这些都必须在文档中妥善地说明清楚。

4. C++相关

4.1 值 vs. 对象

4.1.1 指针 vs. 引用

指针(pointer)还是引用(reference)哪个是最好的输出参数(out-parameters)?

void getHsv(int *h, int *s, int *v) const;
void getHsv(int &h, int &s, int &v) const;

大多数C++书籍推荐尽可能使用引用,基于一个普遍的观点:引用比指针『更加安全和优雅』。与此相反,我们在开发Qt时倾向于指针,因为指针让用户代码可读性更好。比较下面例子:

color.getHsv(&h, &s, &v);
color.getHsv(h, s, v);

只有第一行代码清楚表达出hsv参数在函数调用中非常有可能会被修改。

这也就是说,编译器并不喜欢『出参』,所你应该在新的API中避免使用『出参』,而是返回一个结构体,如下所示:

struct Hsv { int hue, saturation, value };
Hsv getHsv() const;

【译注】:函数的『入参』和『出参』的混用会导致 API 接口语义的混乱,所以,使用指针,在调用的时候,实参需要加上“&”,这样在代码阅读的时候,可以看到是一个『出参』,有利于代码阅读。(但是这样做,在函数内就需要判断指针是否为空的情况,因为引用是不需要判断的,所以,这是一种 trade-off)

另外,如果这样的参数过多的话,最好使用一个结构体来把数据打包,一方面,为一组返回值取个名字,另一方面,这样有利用接口的简单。

4.1.2 按常量引用传参 vs. 按值传参

如果类型大于16字节,按常量引用传参。

如果类型有重型的(non-trivial)拷贝构造函数(copy-constructor)或是重型的析构函数(destructor),按常量引用传参以避免执行这些函数。

对于其它的类型通常应该按值传参。

示例:

void setAge(int age);
void setCategory(QChar cat);
void setName(QLatin1String name);

// const-ref is much faster than running copy-constructor and destructor
void setAlarm(const QSharedPointer<Alarm> &alarm);

// QDate, QTime, QPoint, QPointF, QSize, QSizeF, QRect
// are good examples of other classes you should pass by value.

【译注】:这是传引用和传值的差别了,因为传值会有对像拷贝,传引用则不会。所以,如果对像的构造比较重的话(换句话说,就是对像里的成员变量需要的内存比较大),这就会影响很多性能。所以,为了提高性能,最好是传引用。但是如果传入引用的话,会导致这个对象可能会被改变。所以传入const reference

4.2 虚函数

C++中,当类的成员函数声明为virtual,主要是为了通过在子类重载此函数能够定制函数的行为。将函数声明为virtual的目的是为了让对这个函数已有的调用变成执行实际实例的代码路径。对于没有在类外部调用的函数声明成virtual,你应该事先非常慎重地思考过。

// QTextEdit in Qt 3: member functions that have no reason for being virtual
virtual void resetFormat();
virtual void setUndoDepth( int d );
virtual void setFormat( QTextFormat *f, int flags );
virtual void ensureCursorVisible();
virtual void placeCursor( const QPoint &pos;, QTextCursor **c = 0 );
virtual void moveCursor( CursorAction action, bool select );
virtual void doKeyboardAction( KeyboardAction action );
virtual void removeSelectedText( int selNum = 0 );
virtual void removeSelection( int selNum = 0 );
virtual void setCurrentFont( const QFont &f );
virtual void setOverwriteMode( bool b ) { overWrite = b; }

QTextEditQt 3移植到Qt 4的时候,几乎所有的虚函数都被移除了。有趣的是(但在预料之中),并没有人对此有大的抱怨,为什么?因为Qt 3没用到QTextEdit的多态行为 —— 只有你会;简单地说,没有理由去继承QTextEdit并重写这些函数,除非你自己调用了这些方法。如果在Qt在外部你的应用程序你需要多态,你可以自己添加多态。

【译注】:『多态』的目的只不过是为了实践 —— 『依赖于接口而不是实现』,也就是说,接口是代码抽像的一个非常重要的方式(在Java/Go中都有专门的接口声明语法)。所以,如果没有接口抽像,使用『多态』的意义也就不大了,因为也就没有必要使用『虚函数』了。

4.2.1 避免虚函数

Qt中,我们有很多理由尽量减少虚函数的数量。每一次对虚函数的调用会在函数调用链路中插入一个未掌控的节点(某种程度上使结果更无法预测),使得bug修复变得更复杂。用户在重写的虚函数中可以做很多疯狂的事:

  • 发送事件
  • 发送信号
  • 重新进入事件循环(例如,通过打开一个模态文件对话框)
  • 删除对象(即触发『delete this』)

还有其他很多原因要避免过度使用虚函数:

  • 添加、移动或是删除虚函数都带来二进制兼容问题(binary compatibility/BC
  • 重载虚函数并不容易
  • 编译器几乎不能优化或内联(inline)对虚函数的调用
  • 虚函数调用需要查找虚函数表(v-table),这比普通函数调用慢了2到3倍
  • 虚函数使得类很难按值拷贝(尽管也可以按值拷贝,但是非常混乱并且不建议这样做)

经验告诉我们,没有虚函数的类一般bug更少、维护成本也更低。

一般的经验法则是,除非我们以这个类作为工具集提供而且有很多用户来调用某个类的虚函数,否则这个函数九成不应该设计成虚函数。

【译注】:

  1. 使用虚函数时,你需要对编译器的内部行为非常清楚,否则,你会在使用虚函数时,觉得有好些『古怪』的问题发生。比如在创建数组对象的时候。
  2. C++中,会有一个基础类,这个基础类中已经实现好了很多功能,然后把其中的一些函数放给子类去修改和实现。这种方法在父类和子类都是一组开发人员维护时没有什么问题,但是如果这是两组开发人员,这就会带来很多问题了,就像Qt这样,子类完全无法控制,全世界的开发人员想干什么就干什么。所以,子类的代码和父类的代码在兼容上就会出现很多很多问题。所以,还是上面所说,其实,虚函数应该声明在接口的语义里(这就是设计模式的两个宗旨——依赖于接口,而不是实现;钟爱于组合,而不是继承。也是为什么Java和Go语言使用interface关键字的原因,C++在多态的语义上非常容易滥用)

4.2.2 虚函数 vs. 拷贝

多态对象(polymorphic objects)和值类型的类(value-type classes)两者很难协作好。

包含虚函数的类必须把析构函数声明为虚函数,以防止父类析构时没有清理子类的数据,导致内存泄漏。

如果要使一个类能够拷贝、赋值或按值比较,往往需要拷贝构造函数、赋值操作符(operator =)和相等操作符(operator ==)。

class CopyClass {
public:
    CopyClass();
    CopyClass(const CopyClass &other);
    ~CopyClass();
    CopyClass &operator =(const CopyClass &other);
    bool operator ==(const CopyClass &other) const;
    bool operator !=(const CopyClass &other) const;

    virtual void setValue(int v);
};

如果继承CopyClass这个类,预料之外的事就已经在代码时酝酿了。一般情况下,如果没有虚成员函数和虚析构函数,就不能创建出可以多态的子类。然而,如果存在虚成员函数和虚析构函数,这突然变成了要有子类去继承的理由,而且开始变得复杂了。起初认为只要简单声明上虚操作符重载函数(virtual operators)。 但其实是走上了一条混乱和毁灭之路(破坏了代码的可读性)。看看下面的这个例子:

class OtherClass {
public:
    const CopyClass &instance() const; // 这个方法返回的是什么?可以赋值什么?
};

(这部份还未完成)

【译注】:因为原文上说,这部份并没有完成,所以,我也没有搞懂原文具体也是想表达什么。不过,就标题而言,原文是想说,在多态的情况下拷贝对象所带来的问题??

4.3 关于const

C++的关键词const表明了内容不会改变或是没有副作用。可以应用于简单的值、指针及指针所指的内容,也可以作为一个特别的属性应用于类的成员函数上,表示成员函数不能修改对象的状态。

然而,const本身并没有提供太大的价值 —— 很多编程语言甚至没有类似const的关键词,但是却并没有因此产生问题。实际上,如果你不用函数重载,并在C++源代码用搜索并删除所有的const,几乎总能编译通过并且正常运行。尽量让使用的const保持实用有效,这点很重要。

让我们看一下在QtAPI设计中与const相关的场景。

4.3.1 输入参数:const指针

有输入指针参数的const成员函数,几乎总是const指针参数。

如果函数声明为const,意味着既没有副作用,也不会改变对象的可见状态。那为什么它需要一个没有const限定的输入参数呢?记住const类型的函数通常被其他const类型的函数调用,接收到的一般都是const指针(只要不主动const_cast,我们推荐尽量避免使用const_cast

以前:

bool QWidget::isVisibleTo(QWidget *ancestor) const;
bool QWidget::isEnabledTo(QWidget *ancestor) const;
QPoint QWidget::mapFrom(QWidget *ancestor, const QPoint &pos) const;

QWidget声明了许多非const指针输入参数的const成员函数。注意,这些函数可以修改传入的参数,不能修改对象自己。使用这样的函数常常要借助const_cast转换。如果是const指针输入参数,就可以避免这样的转换了。

之后:

bool QWidget::isVisibleTo(const QWidget *ancestor) const;
bool QWidget::isEnabledTo(const QWidget *ancestor) const;
QPoint QWidget::mapFrom(const QWidget *ancestor, const QPoint &pos) const;

注意,我们在QGraphicsItem中对此做了修正,但是QWidget要等到Qt 5:

bool isVisibleTo(const QGraphicsItem *parent) const;
QPointF mapFromItem (const QGraphicsItem *item, const QPointF &point) const;

4.3.2 返回值:const

调用函数返回的非引用类型的结果,称之为右值(R-value)。

非类(non-class)的右值总是无cv限定类型(cv-unqualified type)。虽然从语法上讲,加上const也可以,但是没什么意义,因为鉴于访问权限这些值是不能改变的。多数现代编译器在编译这样的代码时会提示警告信息。

【译注】:cv-qualified的类型(与cv-unqualified相反)是由const或者volatile或者volatile const限定的类型。详见cv (const and volatile) type qualifiers – C++语言参考

当在类类型(class type)右值上添加const关键字,则禁止访问非const成员函数以及对成员的直接操作。

不加const则没有以上的限制,但几乎没有必要加上const,因为右值对象生存时间(life time)的结束一般在C++清理的时候(通俗的说,下一个分号地方),而对右值对象的修改随着右值对象的生存时间也一起结束了(也就是本条语句的执行完成的时候)。

示例:

struct Foo {
    void setValue(int v) { value = v; }
    int value;
};

Foo foo() {
    return Foo();
}

const Foo cfoo() {
    return Foo();
}

int main() {
    // The following does compile, foo() is non-const R-value which
    // can't be assigned to (this generally requires an L-value)
    // but member access leads to a L-value:
    foo().value = 1; // Ok, but temporary will be thrown away at the end of the full-expression.

    // The following does compile, foo() is non-const R-value which
    // can't be assigned to, but calling (even non-const) member
    // function is fine:
    foo().setValue(1); // Ok, but temporary will be thrown away at the end of the full-expression.

    // The following does _not_compile, foo() is ''const'' R-value
    // with const member which member access can't be assigned to:
    cfoo().value = 1; // Not ok.

    // The following does _not_compile, foo() is ''const'' R-value,
    // one cannot call non-const member functions:
    cfoo().setValue(1); // Not ok
}

【译注】:上述的代码说明,如果返回值不是const的,代码可以顺利编译通过,然而并没有什么卵用,因为那个临时对像马上就被抛弃了。所以,这样的无用的代码最好还是在编译时报个错,以免当时头脑发热想错了,写了一段没用但还以为有用的代码。

4.3.3 返回值:非const的指针还是有const的指针

谈到const函数应该返回非const的指针还是const指针这个话题时,多数人发现在C++中关于『const正确性』(const correctness)在概念上产生了分歧。 问题起源是:const函数本身不能修改对象自身的状态,却可以返回成员的非const指针。返回指针这个简单动作本身既不会影响整个对象的可见状态,当然也不会改变这个函数职责范围内涉及的状态。但是,这却使得程序员可以间接访问并修改对象的状态。

下面的例子演示了通过返回非const指针的const函数绕开const约定(constness)的诸多方式中的一种:

QVariant CustomWidget::inputMethodQuery(Qt::InputMethodQuery query) const {
    moveBy(10, 10); // doesn't compile!
    window()->childAt(mapTo(window(), rect().center()))->moveBy(10, 10); // compiles!
}

返回const指针的函数正是保护以避免这些(可能是不期望的/没有预料到的)副作用,至少是在一定程度上。但哪个函数你会觉得更想返回const指针,或是不止一个函数?

若采用const正确(const-correct)的方法,每个返回某个成员的指针(或多个指向成员的指针)的const函数必须返回const指针。在实践中,很不幸这样的做法将导致无法使用的API

QGraphicsScene scene;
// … populate scene

foreach (const QGraphicsItem *item, scene.items()) {
    item->setPos(qrand() % 500, qrand() % 500); // doesn't compile! item is a const pointer
}

QGraphicsScene::items()是一个const函数,顺着思考看起来这个函数只应该返回const指针。

Qt中,我们几乎只有非const的使用模式。我们选择的是实用路子: 相比滥用非const指针返回类型带来的问题,返回const指针更可能招致过分使用const_cast的问题。

4.3.4 返回值:按值返回 还是 按const引用返回?

若返回的是对象的拷贝,那么返回const引用是更直接的方案; 然而,这样的做法限制了后面想要对这个类的重构(refactor)。 (以d-point的典型做法(idiom)为例,我们可以在任何时候改变Qt类在内存表示(memory representation);但却不能在不破坏二进制兼容性的情况下把改变函数的签名,返回值从const QFoo &变为QFoo。) 基于这个原因,除去对运行速度敏感(speed is critical)而重构不是问题的个别情形(例如,QList::at()),我们一般返回QFoo而不是const QFoo &

【译注】:参看《Effective C++》中条款23:Don’t try to return a reference when you must return an object

4.4.5 const vs. 对象的状态

const正确性(Const correctness)的问题就像C圈子中viemacs的讨论,因为这个话题在很多地方都存在分歧(比如基于指针的函数)。

但通用准则是const函数不能改变类的可见状态。『状态』的意思是『自身以及涉及的职责』。这并不是指非const函数能够改变自身的私有成员,也不是指const函数改变不了。而是指函数是活跃的并存在可见的副作用(visible side effects)。const函数一般没有任何可见的副作用,比如:

QSize size = widget->sizeHint(); // const
widget->move(10, 10); // not const

代理(delegate)负责在其它对象上绘制内容。 它的状态包括它的职责,因此包括在哪个对象做绘制这样的状态。 调用它的绘画行为必然会有副作用; 它改变了它绘制所在设备的外观(及其所关联的状态)。鉴于这些,paint()作为const函数并不合理。 进一步说,任何paint()QIconpaint()的视图函数是const函数也不合理。 没有人会从内部的const函数去调用QIcon::paint(),除非他想显式地绕开const这个特性。 如果是这种情况,使用const_cast会更好。

// QAbstractItemDelegate::paint is const
void QAbstractItemDelegate::paint(QPainter **painter, const QStyleOptionViewItem &option, const QModelIndex &index) const

// QGraphicsItem::paint is not const
void QGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem option, QWidget *widget)

const关键字并不能按你期望的样子起作用。应该考虑将其移除而不是去重载const/非const函数。

5. API的语义和文档

当传值为-1的参数给函数,函数会是什么行为?有很多类似的问题……

是警告、致命错误还是其它?

API需要的是质量保证。API第一个版本一定是不对的;必须对其进行测试。 以阅读使用API的代码的方式编写用例,且验证这样代码是可读的。

还有其他的验证方法,比如

  • 让别人使用API(看了文档或是先不看文档都可以)
  • 给类写文档(包含类的概述和每个函数)

6. 命名的艺术

命名很可能是API设计中最重要的一个问题。类应该叫什么名字?成员函数应该叫什么名字?

6.1 通用的命名规则

有几个规则对于所有类型的命名都等同适用。第一个,之前已经提到过,不要使用缩写。即使是明显的缩写,比如把previous缩写成prev,从长远来看是回报是负的,因为用户必须要记住缩写词的含义。

如果API本身没有一致性,之后事情自然就会越来越糟;例如,Qt 3 中同时存在activatePreviousWindow()fetchPrev()。恪守『不缩写』规则更容易地创建一致性的API

另一个时重要但更微妙的准则是在设计类时应该保持子类名称空间的干净。在Qt 3中,此项准则并没有一直遵循。以QToolButton为例对此进行说明。如果调用QToolButton的 name()caption()text()或者textLabel(),你觉得会返回什么?用Qt设计器在QToolButton上自己先试试吧:

  • name属性是继承自QObject,返回内部的对象名称,用于调试和测试。
  • caption属性继承自QWidget,返回窗口标题,对QToolButton来说毫无意义,因为它在创建的时候parent就存在了。
  • text函数继承自QButton,一般用于按钮。当useTextLabel不为true,才用这个属性。
  • textLabel属性在QToolButton内声明,当useTextLabeltrue时显示在按钮上。

为了可读性,在Qt 4QToolButtonname属性改成了objectNamecaption改成了windowTitle,删除了textLabel属性因为和text属性相同。

当你找不到好的命名时,写文档也是个很好方法:要做的就是尝试为各个条目(item)(如类、方法、枚举值等等)写文档,并用写下的第一句话作为启发。如果找不到一个确切的命名,往往说明这个条目是不该有的。如果所有尝试都失败了,并且你坚信这个概念是合理的,那么就发明一个新名字。像widgeteventfocusbuddy这些命名就是在这一步诞生的。

【译注】:写文档是一个非常好的习惯。写文档的过程其实就是在帮你梳理你的编程思路。很多时候,文档写着写着你就会发现要去改代码去了。除了上述的好处多,写文档还有更多的好处。比如,在写文档的过程中,你发现文字描述过于复杂了,这表明着你的代码或逻辑是复杂的,这就倒逼你去重构你的代码。所以 —— 写文档其实就是写代码

6.2 类的命名

识别出类所在的分组,而不是为每个类都去找个完美的命名。例如,所有Qt 4的能感知模型(model-aware)的item view,类后缀都是ViewQListViewQTableViewQTreeView),而相应的基于itemitem-based)的类后缀是WidgetQListWidgetQTableWidgetQTreeWidget)。

6.3 枚举类型及其值的命名

声明枚举类型时,需要记住在C++中枚举值在使用时不会带上类型(与JavaC#不同)。下面的例子演示了枚举值命名得过于通用的危害:

namespace Qt
{
    enum Corner { TopLeft, BottomRight, ... };
    enum CaseSensitivity { Insensitive, Sensitive };
    ...
};

tabWidget->setCornerWidget(widget, Qt::TopLeft);
str.indexOf("$(QTDIR)", Qt::Insensitive);

在最后一行,Insensitive是什么意思?命名枚举类型的一个准则是在枚举值中至少重复此枚举类型名中的一个元素:

namespace Qt
{
    enum Corner { TopLeftCorner, BottomRightCorner, ... };
    enum CaseSensitivity { CaseInsensitive, CaseSensitive };
    ...
};

tabWidget->setCornerWidget(widget, Qt::TopLeftCorner);
str.indexOf("$(QTDIR)", Qt::CaseInsensitive);

当对枚举值进行或运算并作为某种标志(flag)时,传统的做法是把或运算的结果保存在int型的值中,但这不是类型安全的。Qt 4提供了一个模板类QFlags<T>,其中的T是枚举类型。为了方便使用,Qttypedef重新定义了QFlag类型,所以可以用Qt::Alignment代替QFlags<Qt::AlignmentFlag>

习惯上,枚举类型命名用单数形式(因为它一次只能『持有』一个flag),而持有多个『flag』的类型用复数形式,例如:

enum RectangleEdge { LeftEdge, RightEdge, ... };
typedef QFlags<RectangleEdge> RectangleEdges;

在某些情形下,持有多个『flag』的类型命名用单数形式。对于这种情况,持有的枚举类型名称要求是以Flag为后缀:

enum AlignmentFlag { AlignLeft, AlignTop, ... };
typedef QFlags<AlignmentFlag> Alignment;

6.4 函数和参数的命名

函数命名的第一准则是可以从函数名看出来此函数是否有副作用。在Qt 3中,const函数QString::simplifyWhiteSpace()违反了此准则,因为它返回了一个QString而不是按名称暗示的那样,改变调用它的QString对象。在Qt 4中,此函数重命名为QString::simplified()

虽然参数名不会出现在使用API的代码中,但是它们给程序员提供了重要信息。因为现代的IDE都会在写代码时显示参数名称,所以值得在头文件中给参数起一个恰当的名字并在文档中使用相同的名字。

6.5 布尔类型的gettersetter方法的命名

bool属性的gettersetter方法命名总是很痛苦。getter应该叫做checked()还是isChecked()scrollBarsEnabled()还是areScrollBarEnabled()

Qt 4中,我们套用以下准则为getter命名:

  • 形容词以is为前缀,例子:
    • isChecked()
    • isDown()
    • isEmpty()
    • isMovingEnabled()
  • 然而,修饰名词的形容词没有前缀:
    • scrollBarsEnabled(),而不是areScrollBarsEnabled()
  • 动词没有前缀,也不使用第三人称(-s):
    • acceptDrops(),而不是acceptsDrops()
    • allColumnsShowFocus()
  • 名词一般没有前缀:
    • autoCompletion(),而不是isAutoCompletion()
    • boundaryChecking()
  • 有的时候,没有前缀容易产生误导,这种情况下会加上is前缀:
    • isOpenGLAvailable(),而不是openGL()
    • isDialog(),而不是dialog()
      (一个叫做dialog()的函数,一般会被认为是返回QDialog。)

setter的名字由getter衍生,去掉了前缀后在前面加上了set;例如,setDown()setScrollBarsEnabled()

7. 避免常见陷阱

7.1 简化的陷阱

一个常见的误解是:实现需要写的代码越少,API就设计得越好。应该记住:代码只会写上几次,却要被反复阅读并理解。例如:

QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical, 0, "volume");

这段代码比下面的读起来要难得多(甚至写起来也更难):

QSlider *slider = new QSlider(Qt::Vertical);
slider->setRange(12, 18);
slider->setPageStep(3);
slider->setValue(13);
slider->setObjectName("volume");

【译注】:在有IDE的自动提示的支持下,后者写起来非常方便,而前者还需要看相应的文档。

7.2 布尔参数的陷阱

布尔类型的参数总是带来无法阅读的代码。给现有的函数增加一个bool型的参数几乎永远是一种错误的行为。仍以Qt为例,repaint()有一个bool类型的可选参数用于指定背景是否被擦除。可以写出这样的代码:

widget->repaint(false);

初学者很可能是这样理解的,『不要重新绘制!』,能有多少Qt用户真心知道下面3行是什么意思:

widget->repaint();
widget->repaint(true);
widget->repaint(false);

更好的API设计应该是这样的:

widget->repaint();
widget->repaintWithoutErasing();

Qt 4中,我们通过移除了重新绘制(repaint)而不擦除widget的能力来解决了此问题。Qt 4的双缓冲使这种特性被废弃。

还有更多的例子:

widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding, true);
textEdit->insert("Where's Waldo?", true, true, false);
QRegExp rx("moc_***.c??", false, true);

一个明显的解决方案是bool类型改成枚举类型。我们在Qt 4QString中就是这么做的。对比效果如下:

str.replace("%USER%", user, false);               // Qt 3
str.replace("%USER%", user, Qt::CaseInsensitive); // Qt 4

8. 案例研究

8.1 QProgressBar

为了展示上文各种准则的实际应用。我们来学习一下Qt 3QProgressBarAPI,并与Qt 4中对应的API作比较。

Qt 3中:

class QProgressBar : public QWidget
{
    ...
public:
    int totalSteps() const;
    int progress() const;

    const QString &progressString() const;
    bool percentageVisible() const;
    void setPercentageVisible(bool);

    void setCenterIndicator(bool on);
    bool centerIndicator() const;

    void setIndicatorFollowsStyle(bool);
    bool indicatorFollowsStyle() const;

public slots:
    void reset();
    virtual void setTotalSteps(int totalSteps);
    virtual void setProgress(int progress);
    void setProgress(int progress, int totalSteps);

protected:
    virtual bool setIndicator(QString &progressStr,
                              int progress,
                              int totalSteps);
    ...
};

API相当的复杂和不一致;例如,reset()setTotalSteps()setProgress()是紧密联系的但方法的命名并没明确地表达出来。

改善此API的关键是抓住QProgressBarQt 4QAbstractSpinBox及其子类QSpinBoxQSliderQDail之间的相似性。怎么做?把progresstotalSteps替换为minimummaximumvalue。增加一个valueChanged()消息,再增加一个setRange()函数。

进一步可以观察到progressStringpercentageindicator其实是一回事,即是显示在进度条上的文本。通常这个文本是个百分比,但是可通过setIndicator()设置为任何内容。以下是新的API

virtual QString text() const;
void setTextVisible(bool visible);
bool isTextVisible() const;

默认情况下,显示文本是百分比指示器(percentage indicator),重写text()方法来定制行为。

Qt 3setCenterIndicator()setIndicatorFollowsStyle()是两个影响对齐方式的函数。他们可被一个setAlignment()函数代替:

void setAlignment(Qt::Alignment alignment);

如果开发者未调用setAlignment(),那么对齐方式由风格决定。对于基于Motif的风格,文字内容在中间显示;对于其他风格,在右侧显示。

下面是改善后的QProgressBar API:

class QProgressBar : public QWidget
{
    ...
public:
    void setMinimum(int minimum);
    int minimum() const;
    void setMaximum(int maximum);
    int maximum() const;
    void setRange(int minimum, int maximum);
    int value() const;

    virtual QString text() const;
    void setTextVisible(bool visible);
    bool isTextVisible() const;
    Qt::Alignment alignment() const;
    void setAlignment(Qt::Alignment alignment);

public slots:
    void reset();
    void setValue(int value);

signals:
    void valueChanged(int value);
    ...
};

8.2 QAbstractPrintDialog & QAbstractPageSizeDialog

Qt 4.0有2个幽灵类QAbstractPrintDialogQAbstractPageSizeDialog,作为 QPrintDialogQPageSizeDialog类的父类。这2个类完全没有用,因为QTAPI没有是QAbstractPrint-或是-PageSizeDialog指针作为参数并执行操作。通过篡改qdocQT文档),我们虽然把这2个类隐藏起来了,却成了无用抽象类的典型案例。

这不是说, 的抽象是错的,QPrintDialog应该是需要有个工厂或是其它改变的机制 —— 证据就是它声明中的#ifdef QTOPIA_PRINTDIALOG

8.3 QAbstractItemModel

关于模型/视图(model/view)问题的细节在对应的文档中已经说明得很好了,但需要强调的一个重要的总结是:抽象类不应该仅是所有可能子类的并集(union)。这样『合并所有』的父类几乎不可能是一个好的方案。QAbstractItemModel就犯了这个错误 —— 它实际上就是个QTreeOfTablesModel,结果就导致了一个错综复杂(complicated)的API,而这样的API要让 所有本来设计还不错的子类 去继承。

仅仅增加抽象是不会自动就把API变得更好的。

8.4 QLayoutIterator & QGLayoutIterator

QT 3,创建自定义的布局类需要同时继承QLayoutQGLayoutIterator(命名中的G是指Generic(通用))。QGLayoutIterator子类的实例指针会包装成QLayoutIterator,这样用户可以像和其它的迭代器(iterator)类一样的方式来使用。通过QLayoutIterator可以写出下面这样的代码:

QLayoutIterator it = layout()->iterator();
QLayoutItem **child;
while ((child = it.current()) != 0) {
    if (child->widget() == myWidget) {
        it.takeCurrent();
        return;
    }
    ++it;
}

QT 4,我们干掉了QGLayoutIterator类(以及用于盒子布局和格子布局的内部子类),转而是让QLayout的子类重写itemAt()takeAt()count()

8.5 QImageSink

Qt 3有一整套类用来把完成增量加载的图片传递给一个动画 —— QImageSource/Sink/QASyncIO/QASyncImageIO。由于这些类之前只是用于启用动画的QLabel,完全过度设计了(overkill)。

从中得到的教训就是:对于那些未来可能的还不明朗的需求,不要过早地增加抽象设计。当需求真的出现时,比起一个复杂的系统,在简单的系统新增需求要容易得多。

好烂啊有点差凑合看看还不错很精彩 (没人打分)
Loading...
DHH 谈混合移动应用开发

DHH 谈混合移动应用开发

 

1053-DHHDavid,Ruby on Rails 作者,37signals 合伙人

畅销书作家、演说家、赛车手、业余摄影师、顾家好男人

 

37signals 在2013年2月发布了 Basecamp 的 iPhone app,在此之前我们就使用原生开发(native)还是混合开发(hybrid)做了许多尝试。在2012年项目启动的时候,大多数人都倾向于原生开发。

Facebook 在2012年发布了他们新的 iOS app,为了获得更好的用户体验,他们放弃了原来的 HTML5 混合开发方式。考虑到2010~2011年的时候,HTML 在移动端的性能确实不尽如人意,这个决定在当时看来也在情理之中。2010年的时候我们觉得 iPhone 3G/3GS 够眩够快,但按照现在的标准来看它们就太慢了。因此在为移动应用开发做架构设计时,我们需要考虑新的移动设备的计算能力,而不是那些老的过时的设备。

移动开发架构设计不需要过多考虑设备的性能

我们从一些测试中得出的一个结论是:现在的移动设备计算能力都很强,运行原生应用和 HTML 应用的效果差别不大,而 HTML 开发的成本则要比原生开发小得多。

当然这个结论在某些领域并不太适用。如果你要开发一个 3D 游戏,原生开发方式能够带来更好的游戏体验。但如果你的移动应用象 Basecamp 一样侧重信息处理,为了降低开发成本,你就可以考虑混合开发方式。我们就是如此,下面是我们三代移动产品的发展轨迹:

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (22 人打了分,平均分: 3.36 )
Loading...
HTML6 展望

HTML6 展望

html6

HTML5 概述

HTML5 是 HTML 语言最受欢迎的版本之一,它支持音频和视频、离线存储、移动端、和标签属性等等。还提供了<article>, <section>, <header>这样的标签来帮助开发者更好地组织页面内容。然而 HTML5 规范仍然没有最后定稿,并且它并不是一个真正意义上的语义标记语言。

HTML6 展望

你有没有曾经希望能在 HTML 中使用自定义标签?比如:使用<logo>来显示你的网站logo,还有使用<toolbar>来显示工具栏等等。我们经常使用<div id=”container”>和<div id=”wrapper”>来组织页面,在 HTML6 里我们希望可以直接使用象<container>和<wrapper>这样的自定义标签。

和 XML 一样,HTML6 应该支持 namespace(命名空间),如:xmlns:xhtml=”http://www.w3.org/1999/xhtml”

HTML6 代码样例:

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (37 人打了分,平均分: 3.70 )
Loading...
Google Inbox如何跨平台重用代码?

Google Inbox如何跨平台重用代码?

原文链接《How Google Inbox shares 70% of its code across Android, iOS, and the Web

inbox2-640x264

开发一个移动应用在当下并不是一件容易的事情。如果想要获得最多的用户,你的应用通常需要覆盖 iOS, Android, 和 Web 三大平台。这就意味着同一个应用需要开发三个版本,使用 Objective-C 或者 Swift 开发 iOS 版本,使用 Java 开发 Android 版本,使用 JavaScript/CSS/HTML5 开发 Web 版本。工作量增大的同时也意味着有更多的 bug 需要修复。

这个问题也是 Google 在开发 Google Inbox 时致力要解决的。在最近发布的这款应用中,Google 使用了一些工具实现了70%的代码跨平台复用。

Google Inbox 覆盖 iOS, Android, Web 三个平台,它们使用的是同一个后台代码逻辑,只是前端的用户体验和平台相关特性的实现有所不同。Google 自主开发了一套辅助工具将 Android 版本的 Java 代码逻辑编译为 Objective-C (针对 iOS 平台) 和 JavaScript (针对 Web 浏览器)。 Java 到 JavaScript 的编译由 Google Web Toolkit SDK 完成,Java 到 Objective-C 的编译则由 J2ObjC (j2objc.org)来完成。

J2ObjC 是一个开源项目,由 Google 在2013年发布。Google Sheets (Google Docs 中的电子表格部分) 也使用了 J2ObjC,而 Google Inbox 则是目前使用 J2Objc 最多的 Google 项目。

Google Inbox 复用的代码逻辑包括:对话 (conversations),提醒 (reminders),联系人 (contacts)。还有网络相关功能和离线同步。这些代码逻辑的复用节省了大量的时间和成本。

在产品设计时,Google 将这些可复用功能划分为抽象的逻辑概念,比如:提醒的逻辑放在 “reminder.java” 中,可以被 Android UI 调用。对 iOS 版本而言,J2ObjC 将 “reminder.java” 编译成 Objective-C 代码,再由 iOS UI 调用。

Google 没有跨平台编译 UI 部分的代码,因为不同平台的UI特性各有不同,盲目统一会导致非常糟糕的用户体验。代码复用只是针对可以共享的后台逻辑,前端的UI实现是完全原生 (native) 的。这与 Xamarin (一个基于 Microsoft C# 的跨平台移动开发工具) 提出的概念类似。

跨平台代码复用通常会带来一些性能上的问题。Garrick Toubassi,Engineering Director 和 Google Inbox 项目组成员,对此表示: “性能上的影响如果有的话,也可以说是微不足道的。我们做过大量的性能测试。因为没有加入额外的中间层来处理跨平台兼容性,所有代码最后都是平台原生代码。J2ObjC 编译生成的目标代码和 Java 源代码拥有大致相同的对象数量和对象图谱复杂度 (object graph complexity) ”。

Google 使用的整套方法解决了跨平台移动开发中的一个很重要的问题,同时也推进了安卓先行 (Android-first) 的移动开发策略。

更多 Google Inbox 文章请猛戳 Gmail 官方博客

好烂啊有点差凑合看看还不错很精彩 (36 人打了分,平均分: 4.08 )
Loading...
PFIF网上寻人协议

PFIF网上寻人协议

本文的主要内容来自Wikipedia(http://en.wikipedia.org/wiki/People_Finder_Interchange_Format)

PFIF全称People Finder Interchange Format,是一个应用广泛的数据开源的标准协议,这个协议主要是设计用来在不同的政府、救援组织、或是其它的一些灾难中生存者和其亲人联系的网站间进行数据交换的一种协议。

这个协议基于XML,信息中包括人的身份标识,还有人目前的位置和状态等一些信息。PFIF可以通过Atom和RSS feed出去。PFIF可以允许不同的寻人站点进行数据交换和合并。每一条记录都有一个唯一的标识,这个标识说明了这是由哪个域名创建的。这样,当A站点获得B点的某个人的数据时,在A站点可以对这个人的增加的信息可以转到其它站点上再被增加相关的信息,因为有一个唯一的ID,所以信息可以在不同的站点上被附加。

从wikipedia上看,说起PFIF这个事,得回到2001年的911事件,那时人们一共使用了超过25个不同的在线论坛和网上寻人站来查找相关的亲人和朋友(注:寻人网站英文叫:Survivor Registry,生还者登记网站)。其中一个最大的网站是由伯克利大学的学生Ka-Ping Yee 和 Miriam Walker 开发运行在Millennium计算集群上的safe.millennium.berkeley.edu网站。那时,为了减少各种网站间的混乱,伯克利的寻人网站开始从其它几个比较大的寻人站点收集相关的数据,并人肉整合到一起。

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (27 人打了分,平均分: 4.41 )
Loading...
Why C++ ? 王者归来

Why C++ ? 王者归来

因为又有人邀请我去Quora的C2C网站去回答问题去了,这回是 关于 @laiyonghao 的这篇有点争议的博文《2012 不宜进入的三个技术点》ActionScript,Thread 和 C++, C++争议的争议最大。(要我说,.NET比C++更需要慎重进入,呵)。我就在这里回复一下这个问题吧。

正好我一个月前看到一个视频,这个演讲视频还比较著名,这个演讲者是Exceptional C++ C++ Coding Standards 的作者,还是ISO C++ 委员会的Chair,C++/CLI首席架构师,还是Microsoft的软件架构师,他叫Herb Sutter,他的这个演讲视频是 C++ and Beyond 2011上的一次公开演讲,题目是——Why C++? (如果你觉得那里的视频比较慢,你可以看优酷上的视频)(英文听力好的同学可以看一样,因为都没有中文字幕)

我觉得这篇文章就足够可以说明很多问题了,所以,我把Herb的演讲幻灯片截了几页放到这里,并做上一些注释,算是一个演讲内容摘要吧。

1) 为什么C++?因为 Performance per $,也就是说performance 就是钱,这个分成三个方面,

  • 耗电,芯片的耗电量,移动设备的耗电量,家用电脑的耗电量都和钱有关系。
  • 资源,家用电脑和移动设备上的处理器资源有限,因为要让一般消费者买的起。
  • 体验,在更小的设备上会有更好的体验,有更好的体验就可以挣更多的钱。

移动设备上的耗电量相信用过智能手机的人都知道吧,Android手机的耗电量实在是太大了。就算是iPhone在开启Wifi和3G的情况下耗电量也很快。

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (24 人打了分,平均分: 4.58 )
Loading...
一些有意思的算法代码

一些有意思的算法代码

Keith Schwarz是一个斯坦福大学计算机科学系的讲师。他对编程充满了热情。他的主页上他自己正在实现各种各样的有意思的算法和数据结构,http://www.keithschwarz.com/interesting/, 目前这个网页上有88个(见下面的列表),但这位大哥要干135个,你可以看看他的To-Do List

从这个列表上,我们可以看到,他从去年7月份就在自己实现这些东西了,我把他实现的这些算法转过来,

  • 另一方面我希望这个事可以影响到一些正在学习编程的人。看看别人是怎么学习编程的,希望对你有借鉴作用。
Name Link Date Added Language Description
Binomial Heap (link) 7‑24‑2010 C++ An implementation of a binomial heap data structure for use as a priority queue.
Bounded Priority Queue (link) 7‑24‑2010 C++ An implementation of a priority queue with a fixed upper limit to its size..
Matrix (link) 7‑24‑2010 C++ A collection of classes for manipulating matrices.
VList (link) 8‑16‑2010 Java An implementation of the List abstraction backed by a VList.
Function Wrapper (link) 8‑16‑2010 C++ A C++ wrapper class around unary functions.
String (link) 8‑17‑2010 C++ An implementation of a string abstraction that uses the small string optimization.

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (22 人打了分,平均分: 4.86 )
Loading...
一些文章资源和趣闻

一些文章资源和趣闻

下面是我这段时间来收集的一些有意思的东西。本站这样的文章还很多,如这个这个这个

Javascript Garden,这是学习Javascript最好的网站了。http://bonsaiden.github.com/JavaScript-Garden,这个文档由两具StackOverflow的人写成, Ivo Wetzel(Writing) 和 Zhang Yi Jiang (Design),表示敬意。

想看看Web开发有哪些技术吗?你得看看这个网站:http://stackparts.com/,他对目前几乎所有Web上用得到的技术都分了个类。下面是个抓图。

Mozilla的安全编程规范 https://wiki.mozilla.org/WebAppSec/Secure_Coding_Guidelines Downloads associated to Software development

PHP,Perl, Ruby, Python语法比较http://hyperpolyglot.org/scripting?utm_source

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (8 人打了分,平均分: 5.00 )
Loading...
一些文章和各种资源

一些文章和各种资源

下面是近期收录的一些文章和资源,希望对你有用。

系统方面

Mozilla's Gecko rendering engine main flow
Mozilla's Gecko rendering engine main flow
好烂啊有点差凑合看看还不错很精彩 (18 人打了分,平均分: 5.00 )
Loading...
软件公司的两种管理方式

软件公司的两种管理方式

这篇文章是我的一个外国的同事Gareth推荐给我的,我和他一起工作过一段时间。他之所以觉得非常不错,是因为这篇文章让他身有体会,他觉得我也一定会有体会,并让我考虑一下翻译到我的blog上来。我看完后觉得很有代表性,而且觉得说得太对了,所以翻译过来,希望大家都读一读,最好转给你的公司老板

这篇文章来源于 StakeExchange上的一个问题——“为什么BA和PM的薪水要比程序员要高?”,顶在一楼的回复分析了这个原因,并指出了两种管理文化。

———————————————————正文开始————————————————————

一个简单的回答应该是——“因为在我们的社会里,我们总是会认为薪水和会和职位的层次绑在一起”。但是,这个答案同时也折射出一个事实——我们的薪资是基于我们的所理解的价值,但这并没有解释

  1. 为什么PM(Project Manager)和BA(Business Analysts)在很多软件公司里在组织的上层?
  2. 为什么软件项目团队总是在最底层?

这两个问题真是非常地值得我们去问,去思考。

总体来说,这个世界上存在两种不同的软件公司的组织结构。我把他们叫做 Widget Factory(小商品工厂) 和 Film Crews(电影工作组).

Widget Factories 想要去解决 怎么去激发被X理论所影响的人 。X理论由 McGregor提出,这个理论是说,一般人的本性是懒惰的,工作越少越好,可能的话会逃避工作,大部分人对集体(公司,机构,单位或组织等)的目标不关心,因此管理者需要以强迫,威胁处罚,指导,金钱利益等诱因激发人们的工作源动力。于是,经理总是要去做他下属的工作。于是,基于这种前提下所思考出来的管理方式,很自然的就是——整个团队能够容易地被经理一个人所取代,这种团队中的每一个人都很容易被别人取代,在这种团队里,经理的工作能力不断地被加强。因些,这种公司一般使用树形层级的组织结构,而不是水平式的工作角色。

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (37 人打了分,平均分: 4.81 )
Loading...
Bob大叔和Jim Coplien对TDD的论战

Bob大叔和Jim Coplien对TDD的论战

今年春节时,我写了一篇《TDD并不是看上去的那么美》,在这篇文章中我列举了一些关于使用TDD的一些难点和对TDD的质疑,后来出现了一些争论(可参见那篇文章的评论),以及Todd同学的《TDD到底美不美》,还有infoQ中文上的那个几乎没有营养离线讨论。今天,有网友给我推来一个英文版infoQ的视频——“Coplien and Martin Debate TDD, CDD and Professionalism”,这是2008年2月18日的视频,视频的主角两个人争论TDD好还是不好,一个是敏捷社区的教主级的人物——Robert Martin(大家称之为“Bob大叔”),另一个是C++,OO,多范式编程的大师Jim Coplien(大家都叫他Cope)。这两个人对TDD的见解有分歧。Coplien的很多观点和我之前的不谋而合,而他自己称他是坚决强烈地站在TDD的对立面上。下面是Jim的原话:

I have adopted a very strong position against what particularly the XP community is calling test driven development.

InfoQ的视频很多时候相当的不给力,就像有前列腺的患者撒尿一样,半天都挤不出一滴。不过,好在那里有这两个人对话的摘录。在这里,我给大家摘要一下:

——————————————————正文分割线————————————————————

Coplien首先让Uncle Bob定义了一下TDD,Uncle Bob说明了他的三个法则:(敏捷的同学一定不陌生)

  1. 一个测试驱动的程序员,其不会在写出一个测试失败的Unit Test前,去写一句可用在生产线上的代码。(没有测试之前不要写任何功能代码)
  2. 在编写用于生产线上代码之前,不写过多的测试失败的Unit Test。(只编写刚好能体现一个失败情况的测试代码)
  3. 在现有代码通过Unit Test前,不写更多的用于生产线上的代码。(只编写恰好能通过测试的功能代码)

Coplien说他有意见的不是这三个法则,而是因为这个三个法则是孤立说出来的。Coplien说他和一些咨询师或是Scrum Master参与过很多的项目,他们发现这些项目都有两个问题:

  1. 他们使用TDD的时候,软件没有一个架构或是framework。当然,Kent Beck说——TDD可以驱使你去做架构。但是,TDD和Unit Test 是一回事吗?Unit Test是一个伟大的事,尤其是当你去写API和类库的时候。今天XP所说的TDD和UT很不一样。如果你使用TDD来驱动你的软件系统架构,那么,基本上来说,三个迭代以后,你开发的软件就会crash掉,而且无法再往前开发。 因为什么?因为连软件团队自己都受不了这三个迭代出来的架构,而且你还会发现,你根本没去去重构。
  2. 第二个问题是,TDD这种方法破坏了GUI(图形界面),就算是Kent也说:“你永远不可以在一个漂亮的界面后面隐藏一个糟糕的架构”,Coplien强烈地相信软件的架构是通过界面来发出其光芒。他觉得如果没有一个好的软件架构,这个会影响用户的操作。

Coplien接着说,如果我们使用Uncle Bob的三条法则,我们也许没有什么问题,但Coplien想告诉大家另一个非常重要的事,那就是软件架构。并说:“我根本不接受TDD是软件专业化实践的论点”

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (5 人打了分,平均分: 4.20 )
Loading...
“另类” 设计模式

“另类” 设计模式

下面这篇文章来自这里:http://www.lsd.ic.unicamp.br/~oliva/fun/prog/resign-patterns,这篇文章有点意思了,山寨了我们著名的Design Pattern。这篇文章并不是很容易翻译,也许我翻译的不好,大家多指正。另外,这篇文章将失去原有的趣味在于其使用了经典设计模式的单词很相似的单词,一走眼你还以为是正二八经的设计模式。呵呵。所以,我在下文中,我会保留原有的英文单词,并把真正的23个经典设计模式的英文名放在旁边(灰色)。这篇文章和之前的如何写出无法维护的代码有异曲同工,个人感觉都是比较欢乐的。

 

辞职模式
Resign Patterns
Design Patterns

不合式的非面向项目软件开发病症
Ailments of Unsuitable Project-Disoriented Software
Elements of Reusable Object-Oriented Software
作者Michael Duell

概要

任何一个熟悉那本由四个人写的经典的设计模式书的朋友,应该知道那本书里的模式都是非常优雅和划时代的。然而,不幸的是,从那些老代码中无法提练出这些模式,因为,在出现这些模式前,大家都不会使用模式。因此,这项工作是从大量的代码中提练出一个模式的目录。这些模式都有充足和永恒的示例。希望你能享受阅读这些模式,但千万不要模仿并使用他们!

1. Cremational Patterns 火葬模式 | Creational patterns 创建模式

下面是五个 cremational patterns.
1.1 Abject Poverty  一贫如洗 | Abstract Factory 抽象工厂

Abject Poverty 模式能让你的软件相当难测试和维护, 并且需要巨大的财政支出,预算已经完全赤字。

1.2 Blinder 眼罩模式 | Builder 建造模式

Blinder 模式是一个应急有效的解决方案,其不需要考虑需求在未来的变化。目前,我们还不太清楚我们为什么叫Blinder模式,一种说法是他们会在写代码的时候被设计人员戴上眼罩,另一种说法是他们希望在维护代码的时候挖出双眼。

1.3 Fallacy Method 错误方法 | Factory method 工厂方法

Fallacy方法主要是在于处理一些不明显的案例。代码逻辑看上去是正确的,当只要某想要去测试一下,或是某个不明显的案例发生了,那些代码中的错误也就出现了。

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (14 人打了分,平均分: 4.86 )
Loading...