Browsed by
标签:Programmer

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...
我看绩效考核

我看绩效考核

(本来,这篇文章应该在5月份完成,我拖延症让我今天才完成)

前些天,有几个网友找我谈绩效考核的事,都是在绩效上被差评的朋友。在大致了解情况后,我发现他们感到沮丧和郁闷的原因,不全是自己没有做好事情,他们对于自己没有做好公司交给的事,一方面,持一些疑义,因为我很明显地感到他们和公司对一件是否做好的标准定义有误差,另一方面,他们对于自己的工作上的问题也承认。不过,让他们更多感到沮丧的原因则是,公司、经理或HR和他们的谈话,让他们感觉整个人都被完全否定了,甚至有一种被批斗的感觉。这个感觉实在是太糟糕了。

因为我也有相似的经历,所以,我想在这里写下一篇文章,谈谈自己的对一些绩效考核的感受。先放出我的两个观点:

1)制定目标和绩效,目的不是用来考核人的,而用来改善提高组织和人员业绩和效率的。

2)人是复杂的,人是有状态波动的,任何时候都不应该轻易否定人,绩效考核应该考核的是事情,而不是人。

我个人比较坚持的认为——绩效分应该打给项目,打给产品,打给部门,打给代码,而不是打给人。然而现在的管理体制基本上都是打给人,而很多根本不擅长管理的经理和HR以及很多不会独立思考的吃瓜群众基本上都会把矛头指向个人,所以,当然会有开批斗会的感觉。

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (61 人打了分,平均分: 4.75 )
Loading...
从Gitlab误删除数据库想到的

从Gitlab误删除数据库想到的

昨天,Gitlab.com发生了一个大事,某同学误删了数据库,这个事看似是个低级错误,不过,因为Gitlab把整个过程的细节都全部暴露出来了,所以,可以看到很多东西,而对于类似这样的事情,我自己以前也干过,而在最近的两公司中我也见过(Amazon中见过一次,阿里中见过至少四次),正好通过这个事来说说一下自己的一些感想和观点吧。我先放个观点:你觉得有备份系统就不会丢数据了吗?

事件回顾

整个事件的回顾Gitlab.com在第一时间就放到了Google Doc上,事后,又发了一篇Blog来说明这个事,在这里,我简单的回顾一下这个事件的过程。

首先,一个叫YP的同学在给gitlab的线上数据库做一些负载均衡的工作,在做这个工作时的时候突发了一个情况,Gitlab被DDoS攻击,数据库的使用飙高,在block完攻击者的IP后,发现有个staging的数据库(db2.staging)已经落后生产库4GB的数据,于是YP同学在Fix这个staging库的同步问题的时候,发现db2.staging有各种问题都和主库无法同步,在这个时候,YP同学已经工作的很晚了,在尝试过多个方法后,发现db2.staging都hang在那里,无法同步,于是他想把db2.staging的数据库删除了,这样全新启动一个新的复制,结果呢,删除数据库的命令错误的敲在了生产环境上(db1.cluster),结果导致整个生产数据库被误删除。(陈皓注:这个失败基本上就是 “工作时间过长” + “在多数终端窗口中切换中迷失掉了”

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (71 人打了分,平均分: 4.83 )
Loading...
技术人员的发展之路

技术人员的发展之路

2012年的时候写过一篇叫《程序算法与人生选择》的文章,我用算法来类比如何做选择,说白了就是怎么去计算,但是并没有讲程序员可以发展的方向有哪些。 所以,就算是有这些所谓的方法论,我们可能对自己的发展还是会很纠结和无所事从,尤其是人到了30岁,这种彷徨和迷惑越来越重。虽然我之前也写过一篇《编程年龄和编程技能》的文章,但是还是有很多做技术的人对于自己能否在年纪大时还能去做技术感到没有信心。我猜测,这其中,最大的问题的是,目前从事技术工作的种种负面的经历(比如经常性的加班,被当成棋子或劳动力等等),让人完全看不到希望和前途,尤其是随着年纪越来越大,对未来的越来越没有信心。

同时,也是因为在GIAC的大会被问到,程序员老了怎么办?而在年底这段时间,也和几个朋友在交流中不断地重复谈到个人发展的这个话题。我的人生过半,活到“不惑”的年纪,自然经常性的对什么事都会回头看看总结归纳,所以,在交谈过程中和交谈过后,自己也有一些思考想记录下来。因为我本人也是在这条路上的人,所以,谈不上给他人指导,我同样也是在瞎乱折腾同样每天在思考自己要去哪儿的“一尘世间迷途老生”。况且,我的经历和眼界非常有限,因此,下面的这些关于个人发展的文字和思考必然是受我的眼界和经历所局限的。也欢迎大家补充和指正。

这些东西不一定对,也不一定就是全部,期许可以让你在年底的时候有所思考,在明年的时候有所计划。

一个重要阶段和标志

在讲个人发展之前,我需要先说一下人生中的一个非常重要的阶段——20到30岁!

这个阶段的首要任务,就是提升自己学习能力和解决难题的能力。这是一个非常非常关键的时间段!这个时间段几乎决定着你的未来。

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (161 人打了分,平均分: 4.88 )
Loading...
什么是工程师文化?

什么是工程师文化?

engineer 四年前,我在QCon上演讲了一个《建一支强大的小团队》(整理后的PPT分享于这里)提到了工程师文化,今天,我想在这里再写一篇关于工程师文化的文章,一方面是因为我又有了一些想法和体会,另一方面,因为我也正走在创业的道路,毫无疑问,要建一个有浓重的工程师文化的团队或公司,所以有必要把自己的相关想法形有成白底黑字的“字据”,以供打自己的脸——“要是未来没有做到,这篇文章就打我未来的脸” || “这篇文章太幼稚了,未来的我会打我现在的脸”,当然,如果要打脸,我希望是前者。

Again,这篇文章不是招人的贴子,因为我觉得,招聘第一重要的事,不是发招聘广告或是找猎头挖人,而是先得让自己变成一个能配得上真正工程师的公司,然后再谈吸引人的事

为什么要工程师文化

看看最近二十年来社会的发展,计算机和互联网已经渗透到了这个社会的每一个角落,各式各样的计算机技术成为了整个世界发展的强大引擎,各式各样的创新,无论是业务创新还是技术创新,都是依托于技术的快速演进,技术成了解放生产力提高社会运作的效率的中坚力量。以美帝为首的技术创新公司着着实实的改变着这个世界和人类的生活和生产习惯。

今天,每个从事计算机行业的技术人员都应该感到幸运,因为,我们不但选对了行业,也出生在了正确的时代,可以感受到前所未有的刺激和变化,相比起我们的父辈,我们的人生,能经历这样的时代,实在是一种幸运。所以,选对了职业并出生在了正确的年代的我们,此时只需要思考的一个问题,那就是,我是否呆在了正确的地方用正确的方式做事?

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (50 人打了分,平均分: 4.70 )
Loading...
关于高可用的系统

关于高可用的系统

HighAvailability-BK在《这多年来我一直在钻研的技术》这篇文章中,我讲述了一下,我这么多年来一直在关注的技术领域,其中我多次提到了工业级的软件,我还以为有很多人会问我怎么定义工业级?以及一个高可用性的软件系统应该要怎么干出来?这样我也可以顺理成章的写下这篇文章,但是没有人问,那么,我只好厚颜无耻的自己写下这篇文章了。哈哈。

另外,我在一些讨论高可用系统的地方看到大家只讨论各个公司的技术方案,其实,高可用的系统并不简单的是技术方案,一个高可用的系统其实还包括很多别的东西,所以,我觉得大家对高可用的系统了解的还不全面,为了让大家的认识更全面,所以,我写下这篇文章

理解高可用系统

首先,我们需要理解什么是高可用,英文叫High Availability(Wikipedia词条),基本上来说,就是要让我们的计算环境(包括软硬件)做到full-time的可用性。在设计上一般来说,需要做好如下的设计:

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (37 人打了分,平均分: 4.95 )
Loading...
这多年来我一直在钻研的技术

这多年来我一直在钻研的技术

Architecture Internships Abroad因为我是看到tinyfool 《那些年我赶过的时髦技术趋势》,在赞叹的时候,也让我对我有好些回忆,所以想写一篇回忆贴,本来觉得回忆是件挺让人沮喪的事,因为是老了的表现,但我写着写着,就歪了楼。看来,我还不老,还在拼博。下面是很多我的唠叨,你喜欢就读读,不喜欢就TLDR – Too Long, Don’t Read!

自从98年毕业,到今天,参加工作有18个年头了,加上在大三的时候就为两个在外面接活的老师程序,到今天,写的程序被用到生产线也有18个年头了。

背景经历

要说明我技术上的“性取向”,还得我说说的我的一些背景和经历。

我这18年,大约分三个阶段:

  • 1996年-2000年入门乱来期,大三大四加在银行工作的两年。
    • 用Powerbuilder/Delphi在WindowsNT/SQL Server上做了好多个MIS管理软件,有酒店的,有送水的,有OA的。
    •  用Java的Applet做了一个Web的教学课件,用于在Win95/IE3.0中演示操作系统中的各种调度和算法的动画,得了个全国大学生挑战者杯的鼓励奖。
    •  用Delphi的ISAPI技术以及PHP/ASP给一些公司和大学做过几个网站。

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (59 人打了分,平均分: 4.88 )
Loading...
让我们来谈谈分工

让我们来谈谈分工

Division of Labour昨天,我看到一个新闻——雅虎取消了QA团队,工程师必须自己负责代码质量,并使用持续集成代替QA。 同时,也听到网友说,“听微软做数据库运维的工程师介绍,他们也是把运维工程师和测试工程师取消了,由开发全部完成。每个人都是全栈工程师”。于是,我顺势引用了几年前写过一篇文章《我们需要专职的QA吗?》,并且又鼓吹了一下全栈。当然,一如既往的得到了一些的争议和嘲弄;-)。

有人认为取消QA基本上是公司没钱的象征,这个观点根本不值一驳,属于井底之蛙。有人认为,社会分工是大前提,并批评我说怎么不说把所有的事全干的,把我推向了另外一个极端。另外,你千万不要以为有了分工,QA的工作就保得住了。

就像《乔布斯传》中乔布斯质疑财务制度的时候说的,有时候,很多人都不问为什么,觉得存在的东西都是理所应当的东西。让我们失去了独立思考的机会。分工也是一样。

所以,为了说完整分工这个逻辑。请大家耐住性子,让我就先来谈谈“分工的优缺点”吧。

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (89 人打了分,平均分: 4.54 )
Loading...
Leetcode 编程训练

Leetcode 编程训练

LeetCodeLogo (1)Leetcode这个网站上的题都是一些经典的公司用来面试应聘者的面试题,很多人通过刷这些题来应聘一些喜欢面试算法的公司,比如:Google、微软、Facebook、Amazon之类的这些公司,基本上是应试教育的功利主义。

我做这些题目的不是为了要去应聘这些公司,而是为了锻炼一下自己的算法和编程能力。因为我开始工作的时候基本没有这样的训练算法和编程的网站,除了大学里的“算法和数据结构”里的好些最基础最基础的知识,基本上没有什么训练。所以,当我看到有人在做这些题的时候,我也蠢蠢欲动地想去刷一下。

于是,我花了3-4个月的业余时间,我把Leetcode的154道题全部做完了。(这也是最近我没有太多的时间来写博客的原因,你可以看到我之前做的那个活动中有几个算法题来自于Leetcode)有人说我时间太多了,这里声明一下,我基本上都是利用了晚上10点以后的时间来做这些题的。

LeetCode的题大致分成两类:

1)基础算法的知识。这些题里面有大量的算法题,解这些题都是有套路的,不是用递归(深度优先DFS,广度优先BFS),就是要用动态规划(Dynamic Programming),或是拆半查找(Binary Search),或是回溯(Back tracing),或是分治法(Divide and Conquer),还有大量的对树,数组、链表、字符串和hash表的操作。通过做这些题能让你对这些最基础的算法的思路有非常扎实的了解和训练。对我而言,Dynamic Programming 是我的短板,尤其是一些比较复杂的问题,在推导递推公式上总是有思维的缺陷(数学是我的硬伤),通过做了这些题后,我能感到我在DP的思路上有了很大的收获。

2)编程题。比如:atoi,strstr,add two num,括号匹配,字符串乘法,通配符匹配,文件路径简化,Text Justification,反转单词等等,这些题的Edge Case, Corner Case有很多。这些题需要你想清楚了再干,只要你稍有疏忽,就会有几个case让你痛不欲生,而且一不小心就会让你的代码会写得又臭又长,无法阅读。通过做这些题,可以非常好的训练你对各种情况的考虑,以及你对程序代码组织的掌控(其实就是其中的状态变量)。还记得我在《函数式编程》中说的,程序中的状态是你程序变得复杂难维护的直接原因。

我觉得每个程序员都应该花时间和精力做这些题,因为你会从这些题中得到很大的收益。做完这些题后你一定会明白下面几个道理:

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (74 人打了分,平均分: 4.78 )
Loading...
互联网之子 – Aaron Swartz

互联网之子 – Aaron Swartz

Aaron_Swartz_profile 1986年11月8日,有个叫Aaron Swartz的人在美国芝加哥伊利诺伊州出生。因为他父母创办了一个软件公司,所以,Aaron在3岁的时候就接触到了电脑,然后就着迷了。

我们先通过Aaron Swartz 的青少年时期来看一下他是怎么样的一个天才:

12岁的时候Aaron就创建了一个类似于Wikipedia式的网站(那时还没有Wikipedia),13岁的时候,Aaron赢得为年轻人而设,创作教育及协同非商业网站的ArsDigita Prize比赛首名。 奖品包括参观麻省理工学院及与网际网路界的知名人士见会。

14岁的时候,他就成为了RSS1.0的开发组的一员。(后来,他和 John Gruber一起开发了Markdown)

15岁的时候,进入W3C的 RDF 核心工作组,并写了RFC3870——这个文档描述了一个新的media type – “RDF/XML“,用于定义互联网上的“语义网络

17岁进入斯坦福大学,1年半后,18岁的时候因为受不了教条式的教育缀学,并通过Y Combinator公司的夏季创办人计划成立Infogami软件公司,在那里,他设想了一个Wiki平台来实现他的Internet Open Library——一个开放的网络图书馆。并写了著名的web.py 开发框架。但他觉得自己太年轻,还要有一个合伙人,于是Y Combinator建议他和Reddit合并。于是他在19岁的时候成了Reddit的创始人

虽然Reddit不挣钱,但是相当火,当他20岁的时候(2006年10月),他们把Reddit卖给了Condé Nast出版社,据说挣到了百万美金。然后,他去了这家出版社工作,受不了办公室的那种工作环境,2007年1月离职。

但是,你能想得到这么天才的一个人,于2013年1月11日自杀了么?那年他才26岁。

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (94 人打了分,平均分: 4.89 )
Loading...
「我只是认真」聊聊工匠情怀

「我只是认真」聊聊工匠情怀

(感谢网友 @Hesey小纯纯 投稿  博客 | 原文链接

老罗的Smartisan T1手机发布会很多人应该都看了,发布会的最后老罗凝视着自己的工匠自画像,半晌没说话,随后转过身,慢慢离开舞台,屏幕下方只留下一句话:

我不是为了输赢,我就是认真。

这一瞬间让我想起93年「狮城舌战」的主角蒋昌建,在「人性本善还是人性本恶」的总结陈词最后,以顾城的名句,「黑夜给了我黑色的眼睛,我却用它寻找光明」,把整个辩论赛的氛围推向高潮。

而老罗的这句话,和这句话背后的工匠背景,却以另外一种无声的却震人心魄的力量,敲打着每一个在场的,或是观看着整个发布会的观众的心绪。

「工匠情怀」,我深有体会,就像我在 面向GC的Java编程 一文中所提到的:

优秀程序员的价值,不在于其所掌握的几招屠龙之术,而是在细节中见真著。

如果我们可以一次把事情做对,并且做好,在允许的范围内尽可能追求卓越,为什么不去做呢?

追求卓越,追求完美,追求细节的极致。小时候看到那些修表匠,握着一个小螺丝刀,或是看着电工,用烙铁沾着锡和松香,在那一小寸的世界里,把坏了的地方修好,那种专注的眼神,觉得很厉害。

现在再去回想那些工匠工作的场景,越发觉得钦佩。在我老家有一家刻章的店,在我上幼儿园的时候就已经在那开了很多年了。前段时间需要刻一个章,发现那家店还在,于是走进去,门口坐着一个老人,我确实记不得当年是不是他,不过看这岁数八九不离十。我以前在别的地方刻的章,都是在电脑里设计完图案后,激光刻蚀。但那次老人却是用的手刻,我着实惊呆了。只看他拿出一块红色的印底,右手持着刻刀,开始一下一下地刻着。虽然老人连话都不怎么说得清了,但是工作时那专注的神情,和精湛的手艺,以及最后成品那比机器更完美的效果,着实让我心里非常动容。

阅读全文 Read More

好烂啊有点差凑合看看还不错很精彩 (71 人打了分,平均分: 4.48 )
Loading...
从Code Review 谈如何做技术

从Code Review 谈如何做技术

(这篇文章缘由我的微博,我想多说一些,有些杂乱,想到哪写到哪)

这两天,在微博上表达了一下Code Review的重要性。因为翻看了阿里内部的Review Board上的记录,从上面发现Code Review做得好的是一些比较偏技术的团队,而偏业务的技术团队基本上没有看到Code Review的记录。当然,这并不能说没有记录他们就没有做Code Review,于是,我就问了一下以前在业务团队做过的同事有没有Code Review,他告诉我不但没有Code Review,而且他认为Code Review没用,因为:

1)工期压得太紧,时间连coding都不够,以上线为目的,

2)需求老变,代码的生命周期太短。所以,写好的代码没有任何意义,烂就烂吧,反正与绩效无关。

我心里非常不认同这样的观点,我觉得我是程序员,我是工程师,就像医生一样,不是把病人医好就好了,还要对病人的长期健康负责。对于常见病,要很快地医好病人很简单,下猛药,大量使用抗生素,好得飞快。但大家都知道,这明显是“饮鸩止渴”、“竭泽而渔”的做法。医生需要有责任心和医德,我也觉得程序员工程师也要有相应的责任心和相应的修养。东西交给我我必需要负责,我觉得这种负责和修养不是”做出来“就了事了,而是要到“做漂亮”这个级别,这就是“山寨”和“工业”的差别。而只以“做出来”为目的标准,我只能以为,这样的做法只不过是“按部就班”的堆砌代码罢了,和劳动密集型的“装配生产线”和“砌砖头”没有什么差别,在这种环境里呆着还不如离开。

老实说,因为去年我在业务团队的时候,我的团队也没有做Code Review,原因是多样的。其中一个重要原因是,我刚来阿里,所以,需要做的是在适应阿里的文化,任何公司都有自己的风格和特点,任何公司的做法都有他的理由和成因,对于我这样的一个初来者,首要的是要适应和观察,不要对团队做太多的改动,跟从、理解和信任是融入的关键。(注:在建北京团队和不要专职的测试人员上我都受到了一些阻力),所以跟着团队走没有玩Code Review。干了一年后,觉得我妥协了很多我以前所坚持的东西,觉得自己的标准在降低,想一想后背拔凉拔凉的,所以我决定坚持,而且还要坚持高标准。

阅读全文 Read More

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