C++ 使用之辩 | 效率、抽象及复杂性的权衡

注:本文为 pongba 的三篇文章合辑。
前两篇是同一内容的中(译文)英(原文)版本。
另一篇关于作者往事追忆。


Why C++(中文版 —— 感谢 waterwalk 翻译)

pongba 于 2007-09-14 16:52:00 发布

刘未鹏 (pongba) / 文

waterwalk_ / 译

首先,感谢 waterwalk 的辛勤翻译。他将翻译内容回贴在 原文下方 为了方便阅读,我将其提取并重新编辑后发布。这篇文章原本旨在整理对 C / C++ 争论的一些思考,但由于最初用英文撰写了部分内容,导致阅读不便,对此表示歉意。不过,好在 waterwalk 完成了整篇文章的翻译,现单独发布于此。

问题

为什么使用 C++ ?在离开之前,请尝试回答这个简单的问题。效率,是吗 ?这是众所周知的。然而,当讨论编程语言及其相关问题时,必须非常明确且有针对性。为什么呢 ?再问一个问题:如果效率是使用 C++ 的唯一理由,那么为什么不用 C 呢 ?C 被认为比 C++ 更高效(尽管实际上 C 并没有比 C++ 高出多少,但即使两者效率相同,问题依然存在)。

迷思

你可能会提到 “更好的抽象机制”,因为 C++ 本应是一个更好的 C,它在不牺牲效率的同时,增加了许多高级特性。但问题在于,“开发者是否真的需要这些高级特性 ?

我们常听到 KISS(Keep It Simple, Stupid)原则,也有人声称 C 比 C++ 更符合这一原则,因此应优先使用 C。这种持续不断的争论使得 C 与 C++ 的对比陷入了一个巨大的误区(或许是一种混乱)。令人惊讶的是,许多人似乎更倾向于使用 C,主要理由是 C++ 太难用对。甚至连 Linus 也这样认为

这种现象的最大影响是,当人们在 C 和 C++ 之间权衡时,往往会倾向于选择 C。一旦开始使用 C,人们会很快适应并满足于现状(这种现象在任何语言乃至人类活动中都普遍存在,C++ 也不例外,比如常有人声称 “我用了 XX 语言这么多年,一直用得好好的”,按照这种说法,任何图灵完备的语言都可以用来编程)。于是,即使他们从未尝试过 C++,或者尚未成为合格的 C++ 程序员,他们就开始宣称 C 比 C++ 更好。然而,真实答案往往取决于具体情况

我提到过 “取决于具体情况”,那么具体情况是什么呢 ?显然,在某些领域,C 是更好的选择。例如,设备驱动开发通常不需要复杂的 OOP / GP 技术,而只是简单的数据操作。重要的是程序员清楚系统的工作原理以及他们正在做的事情。那么,操作系统开发呢 ?我本人并未参与过任何操作系统的开发,但阅读过大量操作系统代码(大多是 Unix 的),我的感觉是,操作系统开发的很大一部分也不需要 OOP / GP。

然而,这是否意味着 在所有对效率要求较高的领域,C 都比 C++ 更好 ? 并非如此。

答案

让我们逐一分析。

首先,当人们关注效率时,实际上存在两种效率 —— 时间效率(例如操作系统、运行时库、实时应用程序、高要求系统)和 空间效率(例如各种嵌入式系统)。然而,这种分类并不能帮助我们决定使用 C 还是 C++,因为 C 和 C++ 在时间和空间效率上都很高。真正影响语言选择的因素是业务逻辑(这里的 “业务逻辑” 并非指 “企业应用业务”)。例如,使用 OOP / GP 来表达逻辑是否更好,还是仅使用数据和过程就足够了 ?

从这个角度来看,我们可以大致将应用程序分为两类(前提是关注的是 C / C++,而不是 Java / C# / Ruby / Erlang 等):底层应用程序和高层应用程序。底层应用程序是指那些 OOP / GP 技术几乎无用的场景,其余则归为高层。显然,在所有需要 C / C++ 高效性的领域中,存在大量 “高层” 应用程序(可参考 Bjarne Stroustrup 的主页 上的列表),在这些领域中,抽象的重要性不亚于效率,甚至更为重要。这些正是 C++ 独特适用且优于 C 的地方

不仅如此,即使在程序员自身代码中不使用高级抽象的领域,也有理由使用 C++。为什么呢 ?仅仅因为代码中未使用类或模板,并不意味着不能使用基于类或模板实现的库。鉴于众多方便的 C++ 库(以及即将到来的 TR1 / TR2),我认为在这些场景中使用 C++ 是有充分理由的 —— 你可以在编码时仅使用 C++ 中的 C 核心(以任何你喜欢的方式保持简单),同时还能利用强大的 C++ 库(例如 STL 容器、算法、TR1 / TR2 组件、Boost 库)。

最后,人们常常忽略的一点是 —— 有时,KISS 原则也依赖于抽象。Matthew Wilson 在他的新书《Extended STL,卷 1》的序言中对此进行了清晰的阐述。他分别用 C 和 C++ 编写了两段代码:

// in C
DIR* dir = opendir (" . ");
if (NULL != dir)
{
    struct dirent* de;
    for (; NULL != (de = readdir (dir)); )
    {
        struct stat st;
        if (0 == stat (de->d_name, &st) &&
​            S_IFREG == (st.st_mode & S_IFMT))
        {
            remove (de->d_name);
        }
    }
    closedir (dir);
}
// in C++
readdir_sequence entries (".", readdir_sequence::files);
std::for_each (entries.begin (), entries.end (), ::remove);

在 C++09 中,代码甚至更简单:

// in C++09
std::for_each (readdir_sequence (".", readdir_sequence::files), ::remove);

这正是即使在自身代码中不需要类或模板,也应使用 C++ 的原因 —— 你所使用的方便的 C++ 库确实需要这些特性。同样地,如果高效的容器(或智能指针)能将你从繁琐的手动内存管理中解放出来,那么为什么还要使用原始的 malloc / free 呢 ?如果一个更好的字符串类(这里不是指 std::string,众所周知它并非 C++ 中最佳的字符串类)或正则表达式类能将你从令人反感的字符串处理代码中解脱出来,那么为什么还要手动编写这些代码呢 ?如果一个 transform(或 for_each)能用一行代码简洁清晰地完成任务(当然,C++ 需要支持 lambda 函数才能实现这一点,这正是 C++0x 的目标),那么为什么还要手写 for 循环呢 ?如果高阶函数正是你需要的,那么为什么还要使用笨拙的替代方法呢 ?

KISS 并不等同于 “原始”;KISS 意味着使用最适合的工具来完成工作,这里的 “最适合” 意味着工具应帮助你尽可能直接且简洁地表达思想,同时不降低代码的可读性和可理解性

真正的问题

人们可能会说,相较于正确使用,C++ 更容易被误用,而 C 的复杂性则更容易管理和控制。在 C++ 中,普通程序员可能会编写出大量高度耦合的类,很快就会陷入一团乱麻。然而,这其实是一个单独的问题。一方面,这种情况也可能发生在任何面向对象的语言中,总有一些程序员在尚未理解 HAS - A 和 IS - A 的区别之前,就贸然在类上层层叠加。他们学会了定义类和继承类的语法,便以为自己掌握了面向对象编程的精髓。另一方面,这种问题在 C++ 中显得更为严重,因为 C++ 存在诸多偶然复杂性,阻碍了设计。而且 C++ 非常灵活,几乎所有问题在 C++ 中都有多种解决方案(想想那些 GUI 库),权衡这些选项本身就非常困难。C++ 中的这些偶然复杂性是其历史包袱,而 C++0x 正在努力消除这些复杂性。设计的灵活性本身并非坏事 —— 它可以帮助优秀的设计师完成出色的设计。如果有人抱怨这些特性过于复杂,那么这可能是他自身的问题,而不是语言的问题。或许他根本就不适合进行设计。如果你担心同事会被 C++ 的高级特性所吸引,从而导致项目失败,那么 或许应该制定一份编码标准并严格执行(或者遵循 C++ 社区积累的智慧,或者在必要时,仅使用 C++ 中的 C 或 C with class 部分),而不是因为存在风险就放弃 C++(这些风险其实可以通过政策来避免),因为这样你将无法使用那些 C++ 库

另一方面,存在一个更为重要的心理学问题 —— 如果一门语言中存在某些奇特的特性或角落,那么迟早会有人发现并被其吸引,从而分散了从事真正有用工作的精力(这有点像墨菲定律),更不用说那些可能为真正问题提供(某种程度上)优雅解决方案的语言特性了。人们天生容易被稀缺资源所吸引。推论:技巧和奇特之处是稀缺资源,因此会吸引人们的注意力,更不用说掌握这些技巧还能让人在群体中显得与众不同。总之,即使是无用的技巧也会吸引人们的极大关注

C++ 中有多少阴暗角落 ?C++ 中有多少技巧 ?总的来说,C++ 中有多少偶然复杂性 ?

公平地说,近年来在现代 C++ 中发现的大多数技巧或技术实际上都是由实际需求驱动的,尤其是需要实现高度灵活且通用的类库(例如 Boost 中的那些组件)。而这些技巧也确实(在某种程度上)为实际问题提供了优雅的解决方案。让我们这样想,如果你面临两难境地:要么使用这些奇特的技巧来实现一些有用的东西,要么不做,那么你会如何选择呢 ?我知道 Boost 的开发者们选择了前者 —— 无论多么困难、多么复杂、多么令人反感,他们都要将其实现出来!

但所有这些争论都无法改变一个事实我们理应拥有一种能够让我们用代码清晰表达思想的语言。以 boost.function / boost.bind / boost.tuple 为例,变长模板参数(variadic templates)可以极大地简化这些库的实现(将代码行数减少至原来的 1 / 10),同时代码也 (远远)更加简洁易懂auto、初始化列表(initializer - list)、右值引用(rvalue - reference)、模板别名(template - aliasing)、强类型枚举(strong - typed enums)、委托构造函数(delegating - constructors)、constexpr、对齐(alignments)、继承构造函数(inheriting - constructors)等等,所有这些 C++0x 的特性都有一个共同目标 —— 消除语言中的各种偶然复杂性或尴尬之处

正如 Bjarne Stroustrup 所说,显然 C++ 太过复杂,人们被吓到了,有时甚至会放弃使用 C++。但 “人们需要相对复杂的语言来解决绝对复杂的问题”。我们不能通过减少语言特性来使其更强大。复杂的特性,例如模板甚至多重继承,也是有用的 —— 如果你恰好需要它们,并且非常小心地使用它们,以免自掘坟墓。在 C++ 的所有复杂性中,真正阻碍我们的是 “偶然复杂性”(有人称之为 “尴尬之处”),而不是语言所支持的编程范式(其实只有三种)。这正是我们应该拥抱 C++0x 的重要原因,因为 C++0x 旨在消除那些长期存在的偶然复杂性,同时 使那些奇特的技巧变得不再必要(显然,目前这些技巧堆积如山,翻翻那些 C++ 的书籍,或者看看 Boost 库,你就会明白我的意思),这样我们就能 直观清晰地表达思想

结论

C++ 难用,更难用对。因此,当你决定使用它时,必须小心,并始终 牢记自己的需求所要达到的目的。这里有一个简单的指南:

我们需要高效率吗 ?
如果需要,那么我们需要抽象吗(请仔细思考这一点,因为 很难评估使用 C++ 高级特性是否能够抵消误用这些机制的风险正确的答案取决于程序员的水平、遵循的编码标准以及编码标准的执行情况等)?

如果是,那么使用 C++。如果不是,那么,我们需要用 C++ 库来简化开发吗 ?

如果是,那么使用 C++,但必须始终牢记你在做什么 —— 如果你的代码不需要那些 “漂亮的” 抽象,就不要试图使用,以免陷入其中。不要仅仅因为你在 .cpp 文件中编写代码并且使用的是 C++ 编译器,就使用类或模板

如果不是,那么使用 C,但你可能会想为什么不用 C++ 中属于 C 的那部分核心呢 ?原因还是:人们很容易陷入语言的 “漂亮” 特性中,即使他们还不清楚这些特性是否有用我记不清有多少次自己编写了一大堆类和继承,最后却问自己 “这些类和继承到底有什么用 ?”。因此,如果你能坚持仅使用 C++ 中的 C 或 C with class 部分,并遵循 “让简单的事情保持简单” 的理念;或者你需要将 C 代码迁移到 C++ 中,那么就使用 C++,但要非常小心。另一方面,如果你既不需要抽象机制,也不需要 C++ 库,因为事情非常简单,不需要方便的组件,例如容器和字符串,或者你认为 C++ 能够给项目带来的好处微不足道,不值得冒险,或者团队中没有足够多的人能够用好 C++,那么可能你还是只用 C 更好

底线是:让简单的事情保持简单(但请记住:简单性可以通过使用高级库来实现);必要时才使用抽象(切记不可滥用;遵循良好的设计方法和最佳实践)。


Why C++

pongba 于 2007-09-11 14:19:00 发布刘未鹏 (pongba)

The Problem

So, why C++? Before you frown and turn away, just try to answer this simple question.
Efficiency, right? Everybody knows the answer. But as it turned out, when discussing a programming language or everything related to one, one should be very specific. Now why’s that? Let me ask you another question: if efficiency is the only reason people use C++, then why don’t they just use C? C is admittedly more efficient than C++ (yeah, yeah, I know it has been proved that C isn’t to any significant extent more efficient than C++, so don’t get me wrong here, because even if they are equally efficient, the problem still exists).

The Myth

I know you are going to say “better abstraction mechanism”, because after all C++ is designed to be a better C, one that has uncompromised efficiency and yet at the same time has all those fancy high - level features. But then the problem comes down to “does it really matter if the developers need those fancy features?” I mean, after all we all have been hearing voices about KISS and stuff, and we all have heard about the claim that, compared to C++, C is more KISS so we should use C. This unstoppable argument has turned the comparison between C and C++ into a big myth (or maybe a mess). And surprisingly, it seems that many people do incline to C, the reason mostly being that C++ is so hard to use right. [Even Linus thinks so, too](http://thread.gmane.org/gmane.comp.version - control.git/57643/focus=57918).

The real serious impact of this phenomenon is that it drives more people to C when they’re weighing their options, be they C and C++; and once they start using C, they will soon get satisfied and comfortable with what suffices, experiencing what is called “satisfaction”. This is when they will come out and claim that C actually is a better choice than C++ even though they didn’t actually try to use C++ or they aren’t adequately good C++ programmers at all. The real answer, however, almost always begins with “it depends”.

So, did I say “it depends”? On what? Obviously there’re some areas where C is a better choice than C++. For instance, device driver development is usually something that doesn’t need fancy OOP / GP techniques. It’s just simple data manipulation; what really matters is the programmers know exactly how the system works, and what they’re doing. Now what about OS development? I’m not a guy who’s been involved in any kind of OS development myself, but having read a fair amount of OS code (Unix mostly), I’ve come to feel that there’s a significant part of the OS development that doesn’t need OOP / GP either.

However, does that mean that, in all those areas where efficiency matters, C is a better choice than C++? Not really.

The Answer

Let’s do this case by case.
First of all, when people are concerned about efficiency, there’re really two kinds of efficiency – time efficiency (e.g. OS, runtime, real - time applications, high - demanding systems) and space efficiency (e.g. all sorts of embedded systems). However, this categorization doesn’t really help us determine whether we should use C or C++, because C and C++ are both extremely efficient as to both time and space. What really affects our language choice (between C and C++, of course) is the business logic (here by “business”, I don’t mean the “enterprise application business”). For example, is it better to use OOP / GP to express the logic or is it better off being kept pretty much just about data and procedures?

From this point of view, we can vaguely divide applications into two categories (of course, with the premise that what we’re concerned with is C / C++, not java / c# / ruby / erlang etc.): low - level applications and high - level applications, where low - level applications means the ones where fancy abstractions such as OB / OOP and GP are pretty much of no use, and high - level means all the rest. Now, obviously, of all the areas where C / C++ is used (because of their high - efficiency), there’re a significant number of “high - level” applications (see those listed on Bjarne Stroustrup’s homepage), where abstraction is just as important as, if not more important than efficiency. And those are precisely the places where C++ is used and useful in a unique sense, and where C++ is a better choice than C.

Wait, there’s more. As it turns out, even in those areas where programmers don’t use high - level abstractions in their code per se, there might be a reason they should use C++, too. Why’s that? Just because your code don’t use class or templates doesn’t mean it doesn’t use a library that does. Considering the availability of all the handy C++ library facilities (with tr1 / tr2 coming soon), I think there’s a pretty strong reason to use C++ in these cases - you can stick to the C core of C++ when coding (KISS in any way you want), and at the same time you’ve got some awesome C++ libraries at your disposal (e.g. STL containers and algorithms, tr1 / tr2 components, etc.). And finally, there’s this one thing that’s always ignored by many people - sometimes KISS relies on abstractions. I think Matthew Wilson made a crystal clear point about this in the prologue of his new book “Extended STL, Vol 1”, where he laid down two blocks of code, one written in C and one in C++:

// in C
DIR* dir = opendir (" . ");
if (NULL != dir)
{
    struct dirent* de;
    for (; NULL != (de = readdir (dir)); )
    {
        struct stat st;
        if (0 == stat (de->d_name, &st) &&
​            S_IFREG == (st.st_mode & S_IFMT))
        {
            remove (de->d_name);
        }
    }
    closedir (dir);
}
// in C++
readdir_sequence entries (".", readdir_sequence::files);
std::for_each (entries.begin (), entries.end (), ::remove);

And it’s even simpler in C++09:

// in C++09
std::for_each (readdir_sequence (".", readdir_sequence::files), ::remove);

I think this is exactly the reason why one should use C++ even in those cases where he doesn’t really need class or templates in his own code - the handy C++ libraries he will find very useful does. Similarly, if an efficient container (or a smart pointer) will save you from all the boring job of manual manipulation of memory, then what’s the point of using the primitive malloc / free? If a better string class (I’m not talking about std::string; everybody knows it’s not the best C++ can do) or regex class can relieve you of all the cluttered string - manipulation code you don’t even want to look at, then what’s the point of doing it manually. If a ‘transform’ (or a ‘for_each’) can do your job in one line so succinctly and clearly (and I know, of course, C++ need lambda function support for those - that’s what C++0x is for), then what’s the point of hand - written for - loops? If high - order function is really what you need, then what’s the point of using awkward workarounds to approach the same deal?

KISS doesn’t mean “primitive”; KISS means using the most suitable tool for your job, where “most suitable” means the tool you use should help you express your mind as straight (and succinct) as possible, as long as it doesn’t compromise the readability and understandability of the code.

The Real Problem

People might say that C++ is much more easily misused than properly - used, and C, on the other hand, is always more manageable and controllable as to complexity. In C++, an average programmer might come up with a whole bunch of highly coupled classes that degenerates fast into a big mess. But this is actually a separate issue. On the one hand, it can pretty much occur in any object oriented language. There’re always programmers who dare to write classes on top of classes even before they have any idea what HAS - A is and what IS - A is; they learn all the syntax of defining a class and inheriting one from another and they thought they’ve grasped the essence of OOP. On the other hand, the reason it appears to be more serious in C++ is because C++ has so many accidental complexities that impede the design, and because C++ is so flexible that pretty much every problem in C++ has several alternative solutions (thinking of all the GUI libraries) so that weighing all the options becomes a hard job itself. The accidental complexities are a historical baggage that C++0x is trying so hard to (and hopefully will) get rid of; the flexibility with respect to design isn’t actually a bad thing if you think about it - it helps good designers make good designs; and if someone blame them for hurting his brain then maybe it’s his problem, not the language’s; maybe he shouldn’t be the one to make a design. And if you’re so worried that your fellow C++ coders will be enticed by fancy high - level features and that your project will eventually get screwed, then maybe what you should do is setting up a coding standard and enforce it (or you can just follow the collective wisdom, or stick to the C core or C with class part of C++ if necessary), not flinching away just because there’re risks (risks that can be avoided by policies), because then you will not be able to access all the C++ libraries anymore, mind you.

On the other hand, there’s this more important psychological problem - if there’s a bizarreness in a language, then eventually someone will find it and people will be attracted by it, and it will draw energy from the main people effort of doing something really useful (It’s kind of like the Murphy’s Law), let alone the ones that can lead to an (on some level) elegant solution to a real problem. People are inherently attracted by scarce resources. Corollary: Tricks and bizarrenesses are scarce resources, so they draw people’s attention, not to mention the fact that mastering a trick makes one feel special in the herd. The bottom line is, even useless tricks draw people’s attention so heavily.

How many black corners are there in C++? How many tricks are there in C++? All in all, how many accidental complexities are there in C++?

To be fair, most of the tricks and (you might say) techniques that have been [discovered in recent years](http://www.amazon.com/Modern - Design - Generic - Programming - Patterns/dp/0201704315) (i.e. modern C++) are driven by real needs, particularly the needs to implement highly flexible and generic library components(thinking of all the components in boost). And they did lead to (on some level) elegant solutions to real problems. Think about it this way: if you’re put in a place where either you have to use tricks to implement something really useful or you don’t implement it so other people won’t have the benefit of using it. What would you choose? I know that the boost heroes chose the former - implementing them, no matter how hard and tricky and cumbersome the implementation is.

But all those arguments don’t change the fact that we deserve to have a language that supports a clean way to express our minds in code. Take boost.function / boost.bind / boost.tuple for examples, [variadic templates](http://www.generic - programming.org/~dgregor/cpp/variadic - templates.html) will tremendously simplify (by reducing the LOC to nearly 1 / 10 of the original) the implementation of the three (and many, many more to come) libraries, and the code will become succinct and as simple as possible, too. Auto, initializer - list, rvalue - reference, template - aliasing, strong - typed enums, delegating - constructors, constexpr, alignments, inheriting - constructors, etc; all those [C++0x features](http://herbsutter.spaces.live.com/Blog/cns !2D4327CC297151BB !239.entry), they all have one goal - eliminating the various accidental complexities or embarrassments of the language.

As Bjarne Stroustrup said, obviously C++ is too complicated; obviously people get scared and sometimes turn away. But “people need relatively complex language to deal with absolutely complex problems”. We can’t make a language more powerful by taking features away from it. Complex features like templates and even multiple - inheritance can be useful if they’re exactly what you need, you just have to use them very carefully and by necessity so that you don’t shoot yourself in the foot. Of all the complexities in C++, the ones that really get in our way are the accidental complexities (someone might call them “embarrassments”), not the paradigms the language supports (there’re only three). And that’s a very important reason why we should embrace C++0x, because it aims at eliminating the long - standing accidental complexities C++ had and make obsolete all the arcane tricks (there’s absolutely huge amount of them out there; check all the C++ books and maybe the boost library and you’ll know what I’m talking about) so that we can express our mind clearly and directly.

The Conclusion

C++ is hard, and even harder to use correctly. So when you decide to use it, be careful, always know where you are and what you really want. Here’s a simple guideline:
Do we need to be efficient?

If so, then Do we need abstractions in our code (think very carefully on this one, because it’s very hard to estimate whether the benefit of using the high - level features of C++ outweighs the risk of using them incorrectly; the proper answer depends on how well trained your programmers are, what coding standard you follow and how well it’s enforced, etc.)?

If so, then use C++. Otherwise,Do we need good C++ libraries to ease our job?

If so, then use C++, but meanwhile always remember what you are doing - if your code doesn’t really need all the fancy abstractions, then try not to get sucked into them; don’t use class or templates just because you’re writing code in a .cpp file and using a C++ compiler.

Otherwise, use C, but then you might wonder why not just use the C core of C++. The same reason as always: people get easily sucked into fancy language features even when they don’t really know if they’re going to help - I can’t tell you how many times I wrote a bunch of classes only to find out “what the heck are these classes for?”. So, if you can stick to the C core or C with class part of C++ and keep simple things simple, or if your code needs a migration path from C to C++, use C++ then, but be very careful. On the other hand, if you need neither abstraction mechanisms in your code nor quality C++ libraries because what you’re doing is so simple that you don’t even need convenient components like containers or strings, or you decide that the benefit C++ can bring you in your project is minor to an extent that it’s not even worth taking the risk, or you just simple don’t have enough people that can use C++ in a proper way, then maybe you should stick to C.

The bottom line: keep simple things simple (but remember that simplicity can be achieved by using high - level libraries); use abstractions when necessary (and even then, make spare use of it; follow good design principles and [established good practices](http://www.amazon.com/Coding - Standards - Guidelines - Practices - Depth/dp/0321113586)).


我在南大的七年

pongba 于 2009-05-19 23:43:00 发布

父亲是个对新事物有强烈兴趣的人,村里第一台电视机是他自己组装的,当时全村人都跑过去看,电视机只能收到一个台,CCTV。座机电话是第一个装的。大哥大刚出现的时候,他也是第一个买来用的,那个时候的移动电话真是贵得离谱。

父亲告诉我的第二件最重要的事情是:遇到任何问题,找书去就行。他在自己的专业中完全是自学的。在不属于自己的专业中(后来买了电脑之后需要学习如何架设公司网站,如何网上营销,如何进行电子财务管理,如何使用各种作图软件制图等等)也全都是靠买书自学。

为什么说到这两件事情,因为这是对我一生影响最重大的两个习惯。第一个习惯给了我学习新东西的强烈动机,有了热忱和兴趣,做事情就不觉得累,就自得其乐。第二个习惯则给了我学习任何新东西的方法 —— 不会么?查书去。(当然,学习一门专业并不完全通过看书就行,但这毫无疑问是至关重要的一个途径。)

高三的时候,父亲买了电脑,我立时对这个神奇的事物产生了强烈的兴趣,每期的《电脑爱好者》和《电脑报》都会买来细细看,有时看到各种小工具、技巧还会摘抄下来,回去在自己家里的机器上捣鼓。那个时候我并不知道这样单纯的兴趣会把我引向一条专业的程序员道路。

高三时间变得越来越紧,分配给兴趣的时间越来越少,但兴趣的火花一直都没有熄灭。

跨进南大校门的第一天,我知道,我自由了。

这个自由并不是说我可以做任何事情了,而是我得到了一个重要的决策的自由权,即关于如何利用我的时间。

高考的时候我报了计算机系,但分数差了几分,失之交臂,被调到第二志愿专业 —— 信息与计算科学。当时以为这个专业跟计算机相关的,结果发现是数学系,后来听不少同学提到都上了同样的当。

这里出现了一个歪打正着的事情:我本意并不是上数学系,如果当时知道这个专业是数学系,我可能就不会填报了。但正是因为这个错误,我在数学系好歹也受了一些数学基本功的训练(尽管这个训练的基础是大一上的不多的几节数学分析课,以及每次临考前宿舍哥们例行的 “包夜” 看书),回过头来看这个基本功在后来还是帮了不少的忙,甚至有一阵子我对数学本身到了很感兴趣的程度。不得不说,这段学习的经历是很锻炼抽象和逻辑思维的。另一方面,困难如数学都学了,对其他学科就不觉得难,不会望而却步。

这是我成长过程中的幸运之一。后面还会提到,还有好几次更大的幸运。

大一上学期很快过去,应该是在大一下学期的时候,学校要开一门 C++ 课程。我利用假期先把课本基本啃掉了,当时动机也很简单,先啃掉,就不用上课了嘛。

另一件事情是我经常喜欢去逛书店,看到侯捷的《深入浅出 MFC》上面很多人说这本书好,我当时也对 C++ 有一些基础认识和好感,所以就买下来啃了。一方面侯捷先生写的书的确图文并茂,深入浅出,有意思,另一方面理解一样复杂的东西是个智力挑战。所以看着看着倒是觉得兴致盎然。却不知就这么和 C++ 结下了不解之缘。

这是另一个歪打正着:为什么说是 “歪打” 呢?因为 MFC 的设计也并不能说就是 C++ 的 Best Practice,另一方面若是以用为本的话也未必就要把 MFC 的原理摸个透。所以搞不好现在看来我就不会细看这本书。为什么说是 “正着” 呢?因为理解一个费解的东西本身需要长时间投入注意力,无形中练了理解能力和思维体力(专注),另一方面虽然 MFC 不是最佳设计,但理解里面的代码却加强了对 C++ 本身的认识,这是基本功;也加强了对 C++ 的兴趣,这是动力,后来这个动力驱使了我去看了大量的系统底层知识,从操作系统代码一直看到硬件体系结构。

大二发生了几件重要的事情:一是我在程序员上发表了第一篇技术文章,是剖析 Boost 源码的。我已经不记得什么时候、通过什么途径知道 Boost 这个库的了,总之是知道了,然后也是由于受到侯捷先生源码剖析的影响,也去看源代码,发现很难,越是难就越是觉得有趣,跟踪代码到临晨四点居然越看越精神了,后来火速写了一篇源码剖析。发给《程序员》杂志的技术主编孟岩先生,孟岩先生给了很大的鼓励,于是我很来劲。后来一鼓作气分析了 N 个库,写了一系列的 Boost 源码剖析的文章,在网上随处可以搜到这个系列。

这是第二个歪打正着,按理来说,研究语言技巧并不是程序员最佳的时间投入方法。所以现在我可能不会去做这件事情,会认为有更好的时间投入途径。但当时就一头扎了进去。为什么说也是正着呢?因为虽然这也许不是最佳的投入时间的办法,但总归比什么都不专注要强得多,至少这么一深入,对语言的缺陷和陷阱有了更深刻的认识、也锻炼了对代码的亲切感、跟踪调试的耐心(是的,耐心,而不是技巧)。

所以后来我在博客上总结自己学习编程中走过的弯路,孟岩先生说到,是不是弯路,不是那么容易界定的。

的确,也许真的有更好的路,但事前真的很难判断哪条路是最优的,我们能做到的,是把一条路走透了、走深了,只要不是一条太不靠谱的路,深入的过程中总会有很多的收获。只要不是太顽固,善于反省,总有一天也会逐渐意识到越来越靠谱的路。

除了发表第一篇技术文章之外,大二我还用业余时间做了一些技术翻译,寒假里我坐在家里每天晚上翻译半章《Effective C++》,当然,后来我把译稿提交给出版社的编辑时被告知文笔还显生硬。

同一时间,我继续啃 N 多 C++ 以及底层知识的书,一段时间我的书架上全是这类书,根本不像数学系的学生。非典那阵子,把饭钱都拿来买了书,为什么买得这么疯,也是因为受父亲的一个影响,他告诉我买书不用心疼,因为是长远投资,收益远远大于这点金钱投入。那段时间我边看边写一些代码玩,有模仿 Windows 核心编程的小程序,也有尝试并失败的小游戏,也有拿来对宿舍玩的游戏文件分析的工具,还有为上机考试写的库,总之玩得不亦乐乎;不像很多知名的程序员在学校里面就写了被广为使用的工具,那个时候我完全没有这个意识,也不知道什么是开源,自己自娱自乐而已,所以没有系统训练编码量和编码素养,比较盲目。

大二下半年还发生了一件重要的事情,我在 优快云 上开了一个博客,开始写学习 C++ 和编程的过程中的一些总结。这个博客我一直写到今天,伴随了我整个 7 年的学习和成长,回过头去看就像时光机一样,能够看到一路过来我都关注了些什么东西,是怎么想的,以及对一些事情的看法是怎么改变的。这些东西如果不记录下来,就会逐渐忘掉,也就无法参照过去的自己,对未来提供更好的借鉴了。所以我一直把记录当做一个很重要的工具。另外我也通过这个博客认识了很多朋友,得到了很多的帮助。

后来,学校提供了转系到软件学院的机会,我立即报名了。后来的两年在软件学院度过。但其实反正我也是自己安排时间,所以无甚区别。

大三大四发生了几件重要的事情:一是荣耀先生邀我合译《Imperfect C++》,我很乐意的接了下来,可没想到这本书比我想象得要密度大得多,六百页,而且排版也很密,我给自己安排了每天 6、7 页纸的量,大概花了半年多译完。中间有一段时间停滞,荣耀先生给我鼓劲,告诉我一个重要的方法:如果觉得做不下去了,就硬着头皮坚持做,然后就类似于麻木了,适应了,那种望而却步的感觉会逐渐自动退去。惊人的简单,但事实就是如此,硬着头皮,过了那个情绪上最艰难的时候,也就适应了。这本书译完之后,还是有不少的收获,但我总觉得对性格上的磨练才是最有价值的收获。

二是我开始看英文版的书。之前,由于高中不靠谱的英语教育的原因,我恨死了英语,大二的校内四级课程还挂了科,直到大四才补考。但对技术本身的热爱压过了对英语的反感,我还是硬把一整本影印版啃下来了,而且津津有味,这本书就是 Jeffrey Richter 的《Applied .NET Framework Programming》。这个事情的重要性在于,后来我就不再反感和恐惧英语了,这是其一,其二是我开始意识到英文世界的技术资料有多么丰富,所以虽然本身看上去不是一个太起眼的事件,但却是我获取信息方式的一个 Tipping Point,一旦熟练掌握了语言这个平台,背后就是一扇大门,通向一个海量的信息源,后来我的信息获取绝大多数便来自于英文,其中尤数 wikipedia 和英文版的书为多。另外还有一个收益后面会提到。

大四快毕业的时候又发生了一件事情,微软的 Eric Jiang 通过我的博客找到我,推荐我去微软面试,我随随便便就把粗糙的简历给发过去了,差点因为简历太粗糙被 HR 直接过滤掉。远程电话面了两轮,远程 Coding 一轮,然后记得就是飞到北京面试,住在北航招待所。北京的面试又面了好几轮,有考察底层知识的、有考察 C/C++ 的、.Net 的,还有考察算法的,编码素养的。总之就是公认的基本功考察。最终我还是没能通过面试。个人自己后来总结的结论是算法基本功太差,连什么是动态规划都不知道,编码素养也不够。这部分也是因为本科的学习方法太业余,什么好玩干什么,倒不是说兴趣驱动不好,只是缺乏系统的规划,不清楚也不关心这个领域的蓝图,也弄不清什么是重点。后来在读研的时候恶补了一把算法,好歹弄清了一些基本的概念和思考方法。编码素养的问题也是到了读研的时候才开始思考和学习,现在仍在学习。

另外,在本科阶段,其实我也浪费了很多时间,事实上,是只花了很小一部分时间来学习。之所以还多少学了点东西,完全是仰赖了专注的习惯。而这个专注的习惯其实又是从小受父亲耳濡目染的,父亲会花一整天揣摩一个问题,父亲跟我说过他以前组装电视机时的故事 —— 一切都似乎组装正确,但电视机就是不工作。他苦思冥想,不得其解,当晚,半夜从睡梦中醒来,想到了问题的症结所在。所以,我在啃一些底层知识时如果弄不懂,也会一遍遍读,然后用走路吃饭坐车的时间在脑子里一遍遍去琢磨。我有很多重要的习惯受到父亲的影响,这些习惯自己一般觉察不到,但却默默影响了平时的一点一滴的时间分配和学习轨迹,这些习惯从纸上很难学到,但耳濡目染却会自然而然地学会。

每当有人觉得我本科就做了不少事情的时候,我就会说其实我本科真的浪费了很多时间,而另一方面,这也说明,要掌握一门专业知识,其实每天一点时间,专注、积累和持之以恒也就够了。后来研究生阶段才算真正开始惜时了,于是经历了两年密度很高的学习和思考,心智才成熟了不少。

大四的时候,和很多人一样,我也考研,因为一来也很茫然,二来也希望能够继续有一个宽松的环境继续沉浸在自己的兴趣中。但四年来我都是自己安排时间,逃掉了无数的课,已经对模式化的做题考试产生了抵触,所以考研的复习也没怎么认真准备,那年考研的数学题又偏难,一下慌了神,结果居然把一整页题压在稿纸下忘了做,心理准备有多不充分可见一斑。考完数学我很沮丧,那么大分值的题目没做,数学肯定过不了了,接下来的专业课就没去考了。后来想想其实还是应该去考一考,多少能为下一年积攒经验。

后来就工作了,没去成微软,经同学张振推荐,就去了南京西门子。心里的打算还是边工作边考研,为什么考研,动机也简单,我心理还没准备好,本科只顾着埋头学好玩的,也不看路,不知道自己想要什么样的工作,想做什么样的事情。去西门子之后更加觉得如此,觉得效率很低,做的事情也并不是我乐意的,每天还要在班车上浪费两个小时,于是没过多久就辞掉了工作。打算复习考研。那个时候大概还有半年多的时间才到考研,所以我中途不紧不慢地又翻译了《Exceptional C++ Style》,占用了不少时间,到最后时间很紧了,就剩两三个月,我才开始认起真来,回想起来这是糟糕的时间管理。结果我不得不作了最坏的打算:顶多调剂去软件学院读研(我报的是计算机系),考虑到我反正是自己安排时间,差别应该不大。幸运的是,最终一分不差地过了线,算是蹭到了计算机系里。虽然如此,还是觉得这种惊险不要发生的好,以后或者其他事情上就不会有这么幸运了,及早准备总是很重要的。

读研期间的两年半,是我自己觉得心智年龄成长最迅速的一段时间。这里也有几个很幸运的事情。一个事情是我的导师陈家骏先生给了我很大的自主,于是我得以有时间安排一些重要的学习,这段时间对我来说很重要,我学习和思考了很多东西,为个人以后的发展作了很多准备,倒是没帮导师做什么事情。所以,硕士毕业离开的时候是既感激也愧疚。

另一个事情是认识同实验室的师兄陈怀兴,严格来说是他先来找我聊天,可见那个时候我仍然还是没有意识到与人交流的重要性的,后来,建立了 TopLanguage 讨论组之后越发意识到与他人交流的重要性,也开始主动寻找和参与交流,希望以后自己也能组织交流。陈怀兴对算法很有造诣,也是 TopCoder 上的常客和牛人,那个时候我也正在为以后的工作面试准备一些算法基础,所以经常找他讨论,获益很多。有一句话说:看一个人,只要看他读的书和见的人。还是很有道理的,这两者是一个人成长中最有价值的信息来源。

研一下半年,女朋友找工作的时候需要用到营销方面的知识,于是我去替她找书,偶遇《影响力》这本书,这本书打开了我的视野,让我开始关注一个很有价值的领域:我们如何思考,如何正确地思考。这个领域有很多有意思和有价值的书,我利用近一年的时间,陆陆续续看了近 40 本相关的书(我把这些书整理了之后以豆列的形式放在豆瓣上),对思维的特点和缺陷,以及如何思考有了很多的了解,这些知识后来很大程度上使我更清晰地认识自己,和自己在学习和生活中面临的各种问题。

也是研一下半年,我建立了一个 Google Groups,起名 TopLanguage,一开始的时候是因为平常没人讨论问题,憋得难受,希望有人能够说两句,无心插柳柳成荫,后来这个讨论组的交流越来越多,如今已经近两年,组内成员超过了 4,000 人,两年里我也从中收益颇多,其中最大的收益有两个:一是和人讨论能够激发自己进一步的思考,也促使自己更清晰地表述自己的观点或问题。倒不是说别人就一定告诉你什么新东西,而是讨论对你自己的思维的刺激。二是交流中认识了不少朋友,后来快毕业的时候也受帮助颇多。我一直把 TopLanguage 的创建看作研究生阶段做得最有意义的事情之一。

此外,我有意识地提前准备了英语,因为我相信如果想要去好的外企,口语不过关很可能成为一块短板(当然,英语作为承载最多技术知识的平台语言还有更大的价值),包括阅读、书写和口语。我想了一个方案,可以不用额外花时间来学习英语:阅读的训练蕴含在平时的英文技术资料的阅读中,尽量读英文的,一来英文资料更一手和全面,二来也顺便练阅读。书写的训练蕴含在去国外邮件列表发技术贴和自己写的英文博客文章中。口语的训练则蕴含在平时的娱乐中 —— 美剧,有一个暑假我几乎天天开着 Friends 睡觉,另外学校有国外过来的团队演讲我不再错过,而是主动参加,有一次还带他们出去逛南京,说了一天英语,回头在路上听中文都像英文。虽然和外国友人交流的次数不多,但似乎对口语感觉的提高还挺大。后来在微软亚洲研究院的面试最后一轮就是英语的,而且是偏技术的,好在提前准备了,所以毕竟还是顺利地表达出了想表达的意思。

去微软亚洲研究院面试,是因为幸运地认识了微软亚洲研究院技术创新组项目主管邹欣先生。邹欣先生和他组织的团队在那段时间写了《编程之美》,书中有很多很有意思的题目,而我那段时间恰和陈怀兴讨论算法,在讨论组上也组织了专题的讨论,有了一点粗浅的思考,于是和邹欣先生邮件交流,由于对邹欣先生的技术创新组做的事情很有兴趣,所以找工作的时候便向他毛遂自荐。

承蒙邹欣先生推荐,时隔三年,我再次获得了去微软面试的机会。这一次,由于研究生期间作了一些长远准备,所以心里有底了很多,也就比较冷静了,由于当年知识体系的漏洞被我花功夫补了补,所以面试比较顺利。面试的时候邹欣先生更为详细地介绍了技术创新组的工作,我更加感兴趣了,所以尽管已经有另外几个也不错的选择,但心里还是迅速地做了决定。大约一周后,HR 通知 Offer,我毫不犹豫就接受了。

我想,虽然有很多人本科就明白自己想做什么,我多花了两年多,总还不算太晚。


via:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值