以非耍流氓的方式讨论C++中的面向对象编程

老规矩,先解释一下标题,我承认有点标题党,其实就是为了抓人眼球,希望通过这种标题吸引你点进来看看。我有自信这是一篇好文章。我曾经写过一篇很有名的博客:《为什么C语言不会过时》 很多的网络媒体都进行了转载。我相信这篇博客的质量应该比《为什么C语言不会过时》还好。因为它会颠覆你对C++面向对象编程的传统的认知。

再详细解释一下什么是非耍流氓。其实C++面向对象编程的问题,很多大牛都讨论过,但是因为人家是大牛,所以文字描述居多,很少给出代码。人家毕竟是阅近天下**, 心中早已无码。我个人认为讨论编程的问题,如果你不给出代码就是在耍流氓。 而一些C++初学者,倒是愿意给出一大堆关于什么dog继承animal的类似的源代码,但是这样讨论面向对象编程有些肤浅,片面,甚至是把人带向了错误的方向。所以我这篇文章首先要基于源代码的方式讨论面向对象,而且介绍现代方式的面向对象编程,你会惊讶的发现,无论是从理念上,还是实现方式上,它都和传统意义的面向对象编程截然不同。

我刚开始接触C++的面向对象的时候,首先接触的例子就是duck是animal, student是一个people等等那一套。这种例子铺天盖地,充斥于各种C++的各种初级书本中。我相信大部分人和我都有一样的记忆。当时感觉这个面向貌似没什么难的。但是当自己写程序的时候,大部分时间都根本没怎么使用到继承。这个是第一个层次。

Class Animal{
   Virtual Speak() = 0;
}
Class Duck: Animal{
   Virtual Speak(){cout<<"quack"<<endl;
}

慢慢地开始接触设计模式,这个就有点难度了。这里没有什么鸭子,学生之类的东西了。基本上都是一些很抽象的东西。比如说,策略是一个基类,命令是一个基类等等。当时看的云山雾罩,不得所以。只是机械的记住几个名字,几个类关系图而已。

再慢慢地,开始意识到一些本质的东西。继承是面向对象编程的核心实现方式,其思想本质是把变化的东西封装到子类中,而客户只针对一个基类的指针来调用:

Class Base{
   Virtual Do{}= 0
};
Class Implement1: Base{
   Do(){do it in this way};
}
Class Implement2: Base{
   Do(){do it in thatway};
}

//Client code
Base* Ibase;
Ibase->do(); //

如果你仔细研究设计模式,尤其是建造模式和行为模式,你会发现它们都符合这个基本的想法。例如工厂方法模式,我们把Do 函数换成Factorymethod方法就OK了,换句话说,我们把对象生成方法的变化下推到子类中,使得这种变化不会影响到客户的代码。再举了例子,策略模式,你把Do函数换成AlgorithmInterface()就可以了。我们把算法的变化封装到不同的子类中。当运行的时候调用不同的子类,客户端的代码也不受影响。顺便说一句,这种思想后面还有两个非常凡尔塞的原则。一个是里氏替换原则:如果S是T的子类型,则类型T的对象可以用类型S的对象替换(即类型T的对象可以用子类型S的任何对象替换(即向上转换))而不更改程序的任何期望属性(正确性,任务执行等)。其实就是我们能用Implement2替换掉IBase, 客户的程序不受影响。另外一个就是反向依赖原则:高层模块不应依赖低层模块,低层模块也不应依赖高层模块,两个都应该依赖抽象层。抽象不应该依赖细节,细节应该依赖抽象。 这个其实更简单, 换成人话就是一旦Do()这个玩意定下来,客户就去调用它,子类就去实现它。换句话说,客户不依赖于子类了,而是客户和子类都依赖于Do()这个玩意。这里很多人把Do()这个玩意也叫做接口,抽象,基类,契约等等。 看明白了吗?其实这两个原则其实说的都是一回事。

如果上面我说的你都明白,那么我恭喜你。你对传统意义的面向对象编程已经基本了解了,而且23个设计模式对你来说就不那么难懂了。

在传统的面向对象编程中,你会发现另外的两个问题,1) 什么应该是基类?2)如果基类需要变化怎么办? 而恰巧正是这两点,直接击中了传统面向对象编程的痛点。让我们一个一个说。 首先什么应该是基类?关于这个问题,C++面向对象里面有一个经典的案例,那就是:椭圆应不应该是圆的基类。我这里只是简单的介绍一下,详细的讨论可以参考后面的参考文献1。 长话短说,欧几里得认为圆形就是椭圆,但是C++里面你却不能这么用,这是因为椭圆有两个圆心,但是圆却只有一个圆心。 没有办法直接使用继承。请看源代码;如果使用了继承,你会发现圆这个类有三个圆心。而且setLongRadius在圆形里面也没什么实际的意义。这明显违背了里氏替换原则。

Class ellipse{
   setLongRadius(float);
   setShortRadius(float);
   pair<float, float> center1;
   pair<float, float> center2;
}

Class circle{
   setRadius(float);
   pair<float, float> center;
}

问题出在哪里,到底是欧几里得错了? 还是Bjarne Stroustrup(C++之父)错了?大部分人的第一反应都是一定是我错了。然后针对这个问题,层出不穷的解决方案就出来了。但是无非就三种方法,让基类变弱,让子类变强。或者再建造一个更抽象的shape基类,然后让ellipse和circle都继承于它。但是无论是哪种方法,都相当的别扭和不自然。

如果如何在开始的时候设计类继承体系已经很成问题了,那么未来的变化更是大麻烦。 这里有另外一个例子。比如我们现在设计了一个面向对象的继承体系如下图:

Class Animal{
   walk();
   speak();
}

现在People和Dog继承于它。 貌似非常地完美,没有任何问题。但是两个月以后,我们需要新加入一个类鲨鱼。问题就来了。鱼能继承于Animal吗?不能,因为调用Shark.walk()的方法没有办法解释。但是鱼明明就是一种Animal吗?没关系,面向对象是不会错的,C++语言也是没错的,只是我分类的方法不对。我应该再加入一个哺乳动物类,鱼类,然后把swim这个行为放到鱼这个类中。看着不错,虽然改变类体系这种事情会对现有的系统带来巨大的冲击和影响(几乎所有过去的代码需要改写和重新编译。)但是你相信现在的分类是完美的。但是两个月以后,鲸鱼来了。没关系,我们可以使用双继承去解决这个问题。(一旦使用了双继承,基本上就是代表你的设计需要一个补丁了。)鲸鱼同时继承哺乳动物类和鱼类,也算暂时过关了。但是两个月以后,鸭子来了。这个时候,我估计你快疯了。这个能walk, speak 和swim的东西到底应该放到哪里呢?真没想到,你会被一个鸭子伤害到。 其实你不是个例。 Linux之父炮轰过C++,指出任何现在看起来伟大,光明正确的类设计,两年以后都会有各种问题,那个时候修复起来成本很大,因为整个的系统实现都是基于现有的类设计架构,非常难于修改。同时继承关系是一种很强的耦合关系,这种强耦合使得整个程序变成了一大坨,牵一发而动全身。通过我们上面这个类似玩具的例子,我希望你能对这句话有所体会。

传统面向对象编程(我是指那种上来就去设计复杂的类的分类和继承体系)的问题在于把人类日常科学的分类方法照搬到编程领域,这是不对的。再详细点说,is-a关系并不能完美地适用于编程领域,is-substituable(可替代关系)更适合。在Python语言中,这种思想得到了贯彻和体现。Python语言中有著名的(duck type)鸭子类型。只要你叫起来象鸭子,走路像鸭子,那么你就可以替代鸭子。 至于你本身是什么,我不care。

我们再论is-substituable(可替代关系),现代面向对象的本质到底是什么呢?是构造一个分类准确,漂亮并且复杂的分类体系吗?当然不是!编程的最终目的就是完成一个任务,这个任务需要多个对象配合,换句话说就是各个对象能够彼此发送消息(A对象给B对象发消息就是A对象调用B对象的某个方法。)现代面向对象编程的本质就是轻对象分类体系(少用继承),而重视可替代关系的表达和实现。有了这种可替代关系的加持,对象之间就能更好的彼此合作(对象间调用彼此方法),从而更好地完成一个任务。

一旦我们把is-a关系换成is-substituable(可替换关系),我们就完成了面向对象编程部分的思想改造,但是不要忘记,C++是一门静态语言,不是动态语言。就语言技术本身,我们还不能像Python语言那样直接地支持动态数据类型(鸭子类型)。 但是感谢C++的generic编程。这给现代C++语言打开了一扇新的大门,使得我们不用使用继承也能支持泛型和多态(编译期间的)。不仅如此,现代的C++语言对泛型的支持更安全,对多态的支持更高效。可以这么说,C++11以后,尤其是C++/14/17/20的不断进步和演化,为我们的现代面向对象编程提供了强大的语言技术支持,使得我们完成了现代面向对象编程部分的工具改造


思想和工具都改造完成,我们该上码了。这里我们对一个形状求面积。 我会首先给出基于传统面向对象编程的实现,然后再给出现代面向对象编程的实现。

首先是设计一个形状的基类, 然后派生出三角形, 椭圆,圆形等等。

Class Shape{
   virtual getArea() = 0;
}
Class Rectangle: public Shape{
   setLength(float);
   setHight(float);
   virtual getArea(){return length* hight;}
}
Class Triangle: publich Shape{
   ....
}
float getShapeArea(Shape* ptr_shape){
   return ptr_shape->getArea();
}

代码相当标准和传统,这里不过多解释。请注意,使用传统的面向对象编程,你就会遇到上面我们讨论的所有的面向对象的问题。圆形是不是椭圆?点是不是个形状等等。

好吧,现在我们用现代面向对象编程的思路去解决这个问题。看下面的代码:

Template<typename T>
float getTArea(T t){
   return t.getArea();
}

请注意,并不是使用模板了就实现现代化了,而是这段代码背后的思想。现在我们已经不需要T一定是Shape类型了。相反地,只要在计算面积这个事情上,某个类型T有可替换性,那么它就可以上。 注意到没,我们再也不需要关注椭圆是不是圆形这个问题了。而且以后就算是出现了3D的对象,只要它在计算面积这个事情上有可替换性,那么我们的代码就不需要更改,你只需要不断的加入新类就行了。就这样。

比起Shape基类,T也是类型安全的。因为在C++编译并且实例化这个函数模板的时候,编译器会检查实例化的T类型支不支持getArea()这个函数。在C++20中,我们更是引入了concept的概念。使得对类型的静态检查变得更加简单和方便。

template <typename T>
concept supportArea = requires(T v)
{
    v.getArea()
};
Template<supportArea T> //supportArea is concept
float getTArea(T t){
   return t.getArea();
}

不仅不再需要纠结圆形是不是椭圆这个问题了,我们还可以在可替换性上再前进一步。你想得到面积,我给你面积就得了,至于我怎么算的,你不用担心。并不是我有getArea()函数才有可替代性,而是我把面积返还给你我就有可替代性。假设有个圆形,我们想求面积,但是目前圆形中还没有实现getArea()方法,没关系,我们可以用一个矩形替换圆形来计算面积。这个理解起来很简单。我们可以把一个圆形转换成一个矩形,参考下图

有了现代C++语言特性,实现起来也不难。参看源代码:

Template<Typename T> 
float getTArea(T t){
  if constexpr(Is_Circle<T>::value){
     auto getArea = [](auto& t){
           Rectangle r;
           r.setLengh(pi*t.Radius);
           r.setHigth(t.Radius);
           return r.getArea();
          };
      return getArea();      
  }
  else{
     return t.getArea();
  }  
}

这段代码用到了很多的C++14/17的特性,比如if constexpr,lambda和 type_trait等等。限于篇幅我不多解释了。这里简单介绍一下思想:首先构建一个Is_Circle<T> type_trait类。然后判断T是否是一个圆形,如果是,那么构建一个矩形,然后把矩形的长设置为pi*r,把高度设置为r。然后利用矩形的求面积公式计算圆形的面积。如果不是圆形,那就直接调用类型T自己的getArea。

也就是说,在求面积这件事上,只要在语义层面上满足可替换性,那么一个圆甚至可以被一个矩形所替代,而且我们也彻底摆脱了有没有getArea()函数这种语法层次的的限制。怎么样,够酷吗?

如果这个例子你读懂了,一次类似的例子还可以阅读参考文献2《Linux多线程服务端编程》。第一章里面有一个使用variadic template实现Observer设计模式的例子。书中的一个观点令我印象很深:设计复杂的分类和继承体系就是“叠床架屋”,不如直接基于对象编程(不使用继承,直接使用各种对象彼此协作),这样才能“拳拳到肉”。我深以为然。

这里必须要再提一句,可替代性的度量不仅依赖于某个函数本身的语法和语义限制,它还依赖于调用这个函数的前后条件的限制。也就是pre-condition和post-condition也要同时满足客户的要求。这么说有点抽象,再举个例子,现在有两个鸭子类,调用一个鸭子类的speak方法,它返回“quack”,调用另外一个鸭子类的speak方法,它返回“姐,好久没来了!”。单从speak方法本身,语法和语义都貌似符合可替代性。但是很明显,客户对调用speak方法后的post-codition的期待是完全不一样的。所以在这种情况下,貌似完美的可替代性是不成立的。

最后总结一下:

1)现代的面向对象设计并不是基于Is-a关系设计复杂的类继承体系。而是基于可替代关系构建一个对象间能够高效彼此协作的系统。

2) 可替代关系的理解是基于语义的,而不是基于语法的。例如有的时候语法上不可替代,但是语义上可替代就行。例如圆形即使没有getArea函数,它也可以被矩形替代(在计算面积的时候)。而有的时候,即使语法上完全可以替代,但是postcondition不满足,也不满足替代关系。例如两种不同的鸭子。

3)利用现代C++的语言特性,尤其是基于generic programming语言特性实现上面介绍的基于可替代关系的设计意图。

4)现代面向对象设计并不完全排斥对象,那种没有附加成本的基于数据抽象的对象还是推荐的。例如把一个点对象和数值对象抽象成一个圆形是推荐的。这种对象是更方便我们编程,而且没有任何附加成本。我们反对的是设计复杂的类分类和继承体系。

参考文献:

  1. Inheritance — Proper Inheritance and Substitutability, C++ FAQ (isocpp.org)
  2. Linux多线程服务端编程 (豆瓣) (douban.com)

C++中的未定义和depends

先立个flag, 从此以后有三不聊:不聊政治,不聊宗教,不聊教育孩子。这三个主题有个共同的特点:大家都有自己的观点,大家都认为自己的观点对,但是其实谁的观点还都没啥用!而且这些话题插嘴的门槛还特别底, 这就是为啥容易吵吵起来的原因了。

所以我决定聊点C++语言,不好意思,这个热心网友想插嘴还不太容易。 今天主要聊聊我最近发现的C++的一个现象:那就是模板看起来很难, 其实不难; 异常看起来很简单,其实很难;多线程看起来很难,其实TMD更难。

首先聊模板,talk is cheap,show me the code。下面这段眼花缭乱的代码要是读懂还真不容易,不过他难就难在了语法上,其实就是利用编译期间,模板实例化的时候的SFINAE特点,来判定一个类是否有serialize这个函数。就这么简单。我知道你想骂人了,好端端的你就直接说中文就好,为啥总拽洋文呢? 因为这个词一旦翻译成中文,你要是看到了就想打人了。 这个单词就是“替换失败不是错误”这几个词的首字母缩写。不知道你怎么想,我个人觉得,同样是不懂,但是英文说出来更牛逼一点,是不?


template struct hasSerialize{
typedef char yes[1];
typedef yes no[2];
template struct reallyHas;
template static yes& test(reallyHas* /unused/) { }
template static no& test(…) { /* dark matter */ }
static const bool value = sizeof(test(0)) == sizeof(yes);
};

std::cout << hasSerialize::value << std::endl;

至于语法上花了呼哨的,其实就是一个习惯的问题,一旦你习惯了这种语法,你再看hello world啥的还觉得别扭呢! 好了总结一下,模板编程大部分都是语法上叠屋架床的,其实语义层面相当直接和简单。抓住了语义,那就是一拳到肉。至于语法开始麻烦点,大不了面向stack overflow编程就好。对了提醒一下,一般stack overflow上分为提问区和解答区,抄代码的时候一定要从解答区copy! 看看,这就是一拳到肉!

聊完了模板,现在聊聊异常,介绍C++异常的时候通常都会提到一个小函数,这个函数就是assert。这个函数简单的不得了,就是输入一个判断bool的表达式,如果表达式为false, 那么assert就终止整个程序。好吧,上代码:
assert(2+2==4);

就这么简单吗?对,就这么简单。那么下一个问题就来了,判断表达式判断啥?,assert该在哪里用呢?嗯~~, 这个depends。

我知道你又想打人了,刚刚明白了上面比较复杂的语法,现在好不容易遇到一个简单的,就是想知道在哪里用,你还说depends。这种感觉就像你把500块钱刚给了大街上一位向你招手的女孩,女孩收下钱后马上对你说你是一个好人。这就是一种想打人的感觉。 好吧,我换一种说法,assert就是用来验证是否违背了(invariant)不变性的时候用的。你现在感觉好点了吗? OK,那么什么又是(invariant)不变性呢? 举个例子吧,你今年20,再过一年你21,你遵守了(invariant)不变性,如果你今年20,过一年你还是20,那么你破坏了(invariant)不变性。来吧,打我啊!

没错,你没看错,(invariant)不变性其实是一个更大的概念,它就是指程序在语义上面是正确的,过了一年长一岁,这个在逻辑语义上是对的。 或者在一个更大的范围内是正确并且稳定的。例如,王健林为了让你完成一个小目标,给你转了一个亿。 在王健林的钱离开他的账号,但是还没到你的账号的时候,你们之间的(invariant)不变性就被打破了,直到钱转到你的账号上,你们之间的(invariant)不变性就又恢复了。不变性并不是指的“不变性”。验证这种(invariant)不变性其实是对编程以外的领域知识要非常了解的。例如:对一个人员管理系统,你应该用下面的assert语句。问题是,你真的确定100是正确的吗?你对全世界人口的极大值了解吗?
assert(age>0)
assert(age<100),

哪里会破坏(invariant)不变性呢,这个在多线程里面最多了。例如如果我们有两个线程分别计算你的岁数,每个线程把你的年龄加上0.5,这个时候如果同步的不对,就会发生data race。例如一个线程读入20,加上0.5后,还没写入到内存的时候,另外一个线程切入,读入20,加上0.5后,写回到内存。这个时候第一个线程再次回归,把自己计算得到的20.5再写一遍内存。最后两个线程结束后,内存里保存的就是20.5,而不是21了。这个时候你已经违背了(invariant)不变性了。

除了容易发生违背了(invariant)不变性以外,多线程另外一个问题就是语法和语义都很难,大量的模板推高了使用线程的语法的难度。语义上,哪怕一个简单的锁,什么时候用?哪里用?用不用?通通depends。 原子编程的内存模型又把语义方面的知识推到了CPU的instrument那个层次上去了。

上面说到多线程非常容易破坏数据的(invariant)不变性,首先这种破坏通常很难调试,因为他们并不是每次复现。另外破坏数据的(invariant)不变性还有一个更糟糕的结果,那就是程序的行为“未定义”。

C++新手看到“未定义”这一个词通常会有一种乐观的情绪。他们刚被“面向对象”糊弄到编程这个领域,既然是“未定义”,那么也许是“洞房花烛夜”也保不齐。一般C++老手会告诫新手,未定义通常是“洞房花烛夜-不举”。而10年以上的老鸟会说:未定义通常是“洞房花烛夜-新娘不举”。20年以上的骨灰这个时候一般是不说话的,他们一般45度斜头上望,眼睛里留下两行浊泪,嘴里嘟囔着:“那一年,我洞房花烛夜-新娘很举”。

希望你们能通过这个故事记住“未定义”这个词。对这个词到底有多坏一定要有充足的想象力。说简单点,标准委员会也不知道某些行为会坏到什么程度,那就叫做“未定义”吧。就像一个人长的丑得他妈都不愿意看,那么这种丑就叫做“未定义”。

总结一下:
1)模板在唬人的语法下面其实挺简单的,理解了它到底要干什么,那就一拳到肉了。
2)遇到depends的时候,通常意味着你需要更深入的领域知识了。
3)能不用多线程就不用,必须要用就用成熟的线程库和模型,例如boost线程池库,生产者-消费者模型,消息传递模型等。
4)如果你对“未定义”这个词感触不深,那么说明你还没到“那一年…”

米粒和斐波那契

好久没有写文章了,也不太想写非技术类的文章了。上一篇文章中顺便聊了聊国事, 说华为遭到制裁后日子不太好过。没想到居然有一些网友对我提出了尖锐的批评。他们居然给我列出了华为的具体财报,批评我这是公知,带节奏,甚至是乳滑!这份洋洋洒洒的财报和这顶巨大的帽子确实震撼到我了。我就是闹着玩,没想到他们下死手。我在这里先道个歉,对不起,是我肤浅了。

不过回头一想觉得有点奇怪,上网这么久,大部分网友(也包括我自己)的德行我还是了解的。文章超过三十行就不看了。大部分都是回复不超过三句话,三句话里至少有一句还是下三路。这种正气凛然,有理有据,洋洋洒洒的网友我还真头一次见到。打个比方吧。大部分普通狗到了树林里,基本都是看到一个树就抬起后腿尿几滴,然后就乐的不行不行的,屁颠屁颠的找下一颗树了。但是如果你突然发现有一个狗,围绕着一个树不断的嗅来嗅去,不断的绕圈,偶尔还狂吠几声,那么这一定是一只警犬无疑。再说说乳滑这事,也不知道怎么最近就这么流行。 我就特别好奇,要是别人看见你就想到乳滑,是不是你上衣扣子没系好啊?

既然不能聊国事,就聊聊家事。 聊聊教育孩子。其实我挺不愿意聊这个的。因为我个人认为各种教育理念,各种教育方法,在没有双盲实验的对比下,都是扯淡。就像吃七种蔬菜不得癌症;做一个动作治好七种疾病;奶茶热量高,凉着喝就没事;可乐不健康,那咱就加枸杞等等。 说白了就是自己不想下真功夫,表面上还得显得特别的有道理! 据我所知,出于伦理和道德的考虑,目前还没有一次关于教育方法的双盲实验来证明那种方法是有效的。就像是在没有科学的医学统计方法之前,各种偏方,各种专家那也是漫天飞舞。 如果你感冒了,有人说吃橘子,有人说喝开水,还有专家说板蓝根。如果你听了专家的建议去吃板蓝根,可能发现病的更重了。如果你还想质疑专家的话,专家会告诉你,他也吃板蓝根,而且他也病的更重了。毕竟你只问:“感冒吃什么?”人家专家说:“吃板蓝根。”整个对话逻辑相当严谨,没毛病!

不仅没啥对错,而且这个话题还蛮得罪人的。大家都认为自己的孩子那都是文曲星转世,** 的奇才。(**泛指任何东西,取决于父母的想象力。) 自己的孩子,怎么看都是人品贵重,深肖朕躬,在自己独特的教育理念支持下,必能克承大统。谁愿意听你BB。

既然没啥对错,还得罪人,为啥还要聊呢? 只是感觉有些认为逻辑相当严谨的地方,貌似多少有点问题,就像有些词:“小象”,“大虾”,“学龄前教育”。你每天沉浸其中,感觉相当自然,但是如果你仔细想,这些词本身都有问题。另外让我下定决心聊聊这个事的一个原因是因为米粒。 米粒是我的女儿,上小学4年纪。有一天出去和小朋友玩,回来后有点不高兴,我问她怎么了。她说她的两个小伙伴聊斐波那契数列,而她不知道是什么?再详细一问,原来两个小伙伴在上code的课外补习班。我听了不仅莞尔,翻译成白话就是差点没笑出猪叫。你老爸虽然百无一用,论起斐波那契,那还真是关公面前耍大刀啊! 我顺手在纸上写下了1, 1, 2, 3, 5, 8, 13, ? 我告诉她下一个数就是前两个数的和。我准备等她告诉我下一个问号等于21后,我就要开始放大招了。首先介绍递归,然后介绍如何理解“迭代是人,递归是神”这句话,再然后引入动态规划的思想,并且介绍引入动态规划后,算法的复杂度如何从指数级别降为线性级别,再然后我想介绍C++语言的模板元编程,由于C++元编程是图灵完备的,我们可以直接将算法复杂度变成常数级别O(1),再然后。。。正当我内心细心备课,并幻想着米粒ACM程序设计大赛斩获金牌的时候,我突然发现小米粒盯着问号不说话了,她好像卡在了8+13等于几了?

突然间,我改主意了。我抱了米粒一下,拍了拍她的头, 告诉她:“斐波那契”这个东西不是你这个年龄该知道的,别人知道也并不代表这有多重要,或者有多了不起。等你上了高中或者大学,老师一教,你也就知道了。你虽然不知道“斐波那契”,但是你知道“哈里波特”啊!去看你的哈利波特吧!米粒听完以后高兴的离开了。 不是我凡尔赛,米粒已经把全套的哈利波特都看完了,不是电影,是书!那种给大人看的,没有一副插图的书!厚厚的好几大本。米粒看书的热情,速度以及能力真是远远超过我的想象。哈利波特这种书对我来说太难了!我一看封皮就中了书中的魔法,困的不行!

安慰好了米粒,我开始思考起来。我该如何教育米粒呢?让她学了一年小提琴,最后我实在懒得送,于是放弃了。米粒妈妈总让我教孩子coding,虽然当年我在大学教书的时候,那也是计院里的头牌,但是一想到教孩子这种纯体力活,还是心里很打怵。我该如何理直气壮的为自己的懒惰找接口呢?

其实学习也好,人生也罢,我个人认为都是一场均值回归,这是一个金融学的概念,原本的含义就是一只股票可能涨涨落落的,但是最终会回归到一个稳定的价格上,而这个价格就是这个股票本身该有的价值。这个不以人的意愿而转移的。说回到孩子的教育,孩子的教育其实也有三场均值回归。小学的时候你可以玩命给孩子上各种补习班,但是一旦你的孩子上了初中,那个通过超前学习和强化练习等营造的神童突然就消失了,你会发现你的孩子成绩突然就变得中等了。 毕竟你在小学给她补过的初中的课貌似只是增加了他的熟练程度,而当别人也开始接触并且熟练了以后,这种先发优势大踏步的回归到均值了。

当然, 你可以继续在初中给孩子补课,不过高中和大学的时候你会迎来第二场均值回归。 引起这次回归的原因主要是智商。这个时候的大部分知识已经不是靠熟练就能解决的问题了。尤其是数理化方面的东西,智商所起的作用越来越大,而补课和强化练习的作用越来越少。别人看起来是送分题,对你来说就是送命题。 你会发现所有过去的奥数课,围棋课,并不会增加孩子的智商。因为智商这东西先天就是一个随机分布(上帝丢一个特殊的色子,丢几百万次能出一个“牛顿”,能出一万次“牛”,剩下的就都是”顿”了)。再说的难听点,如果你的孩子需要长期的补课,那么他根本上不了清华北大。这是因为清华北大的孩子大部分智商都在120以上(这个还真有数据支持!)。

别着急,还有第三场均值回归,等你的孩子进入社会,他们会迎来第三场均值回归,这场均值回归更多的取决于其他的因素,原生家庭,性格,配偶,工作,甚至运气等等。你的整个人生的知识巅峰会定格在高中时代,那个时候你上能解二元方程,下能背元素周期表。而现在,你的知识,你的智商根本帮不了你,因为社会是一个混沌系统,毫无规律和道理可讲。

我知道你下手重,但是我还是说话直,不好意思。最终孩子们会回归到某一个你没法控制,且远低于你预期的稳定值上。 不管你承认与否,就是这样。 人生毕竟要经过三个阶段才算完整:你认识到你父母很普通,你认识到你很普通,你认识到你的孩子很普通。

如果你一定要争辩:那你怎么知道我孩子一定不能考第一?没有努力就没有成功!好吧,这碗鸡汤我先干为敬,然后我回敬一碗:你怎么知道你孩子一定能考第一?如果你偏要迎风扫地,我真心希望你别这么努力。其实我想说的是,第一有那么重要吗?天才有那么幸福吗? 牛顿一辈子单身, 图灵最后自杀。图灵公认为是程序员的祖师爷,你也知道,我们程序员虽然暴毙的比较多,但是很少自杀,可见图灵的一生相当压抑,这种天才的生活真的是你想要的吗? 什么才是正确的努力方向呢?

我感觉正确的努力方向有三点:
第一要有一个好身体。花费那么多时间,金钱在各种补习班上,不如带孩子锻炼一下。 踢踢球,游游泳。不用指望着游泳拿奥运冠军,只要以后和婆婆一起掉水里,自己能游回来就行了!

第二要会做饭,任何时候,没有任何问题是一顿BBQ解决不了的,如果有,那就再加一顿火锅。无论你的情操多高,在这个操蛋的社会里,难免遇到操蛋的人和事。心情不好的时候,自己做顿鲇鱼茄子外加韭菜盒子。吃完如果还有闲工夫,再画个画,弹个琴啥的。情操这种高档货,必须先把肚子吃饱了再去陶冶。

第三要有一个完备的性格。不仅积极,而且乐观。 内心强大到藐视所有的藐视。 说实话,我还不知道该如何培养一个孩子具有一个强大的内心和一个积极的性格,但是我知道,一个压抑,忙碌的,不能出去玩的童年那绝对是减分项。

鸡娃无论怎么激,灰鸡不能变飞机。老骂小鸡笨东西,不如自己鸡自己。 身为父母,让孩子念一遍上面这句话,然后再做个决定,看看是不是该送孩子去什么小主持人班。

本文都是个人观点。 如果你感觉你的孩子有天分,也别浪费了。你该送什么班就送什么班,该学什么才艺就学什么才艺。 像牛顿,爱因斯坦,肖邦等这些孩子,那是上帝送给整个人类的礼物,这种孩子天赋异禀。交到你手里,你应该代替全人类把这些天才照顾好,教育好。 纵观人类历史,青史留名的往多算,也就几万人。所以你的孩子会以99.9999999% 的概率成为一个普通人。这是上帝送给你的礼物,这种孩子天赋一饼。交到你手里,你应该教会他们做鲇鱼茄子,让他们就着饼吃,开开心心地长大。

最后放一张米粒在父亲节送给我的一幅画,如果没有小米粒这副画,我都不知道还有一个父亲节。这个父亲节几乎没有任何商家的宣传,看来任何国家,任何文化里都一个德行。从商业价值来看,孩子>妈妈>狗>爸爸。

米粒愿意读书,愿意画一些很幼稚的画,这些画永远也拿不到什么奖,不过who care! 我希望米粒长大后成为一名儿童书籍作家,书中最好配上她自己傻傻的画。就像当年明月说过一句话:“成功就是以自己喜欢的方式,度过一生。”

赵岩,作于父亲节晚。

冰雪奇缘之魔幻休斯顿篇

从2月15号开始,休斯顿度过了最为魔幻的一周,简单说就是一股西伯利亚的寒流横贯美国大陆。对不起我不是气象专家,所以寒流可能也不是来自西伯利亚。我小的时候最经常听到的两个词就是“西伯利亚”和“阿沛阿旺晋美”。那个时候也不知道这个两个词是什么意思,只知道“西伯利亚”一来,天就要冷了。“阿沛阿旺晋美”一来,北京就要开会了。所以这里就借用一下“西伯利亚”这个词了。虽然寒流的来源可能是假的,但是降温却是真的。寒流使得休斯顿这个南方城市出现了有史以来最低温度—-零下11度。

结果就是:整个德州一下被冻住了。首先是大面积的停电。停电造成了很多家庭没办法供暖,南方房子的保温又非常差,没有供暖的结果使得很多家庭的水管被严寒冻爆,家里水流遍地。冻爆的水管又造成了大面积的停水。受灾轻一点的就是又冷又饿,严重的就是各种火灾和煤气中毒以及各种雪天的交通事故。因为严寒,德州大约死亡了20个人,总统宣布德州进入灾难状态!

个人来说,周二凌晨停电的时候,家里的温度已经降到15度以下了。更为绝望的是电力公司发出了通知,如果现在还没来电,那么可能一天之内都不会来了。没有办法,开始研究家里从来没有用过的壁炉。中国人普遍没有使用壁炉的习惯,所以自从搬进现在的房子,壁炉就没有使用过。不过作为一个东北的孩子,凭借着儿时对火炕的记忆,还是最终修好了壁炉。这里简单科普一下,火炕就是东北一种用砖头做成的床,中间中空的部分被用作烟道。火炕在东北是最高级的待客之道,家里一旦来了女宾,主人都会热情地招呼:“赶快把鞋脱了,上炕!” 南方人不会理解,因为他们不可能对家里的女客人说:“赶快把鞋脱了,上床!”

虽然有了壁炉,但周二晚上的温度还是降到10度以下。一家人只能睡在壁炉的前面。细心的米粒担心壁炉里的火会烧出来,米粒的妈妈非常贴心地想出来一个主意。她让我把脚伸到壁炉的前面,这样如果火烧出来,我就会第一个醒了。有了这种安排,米粒才安心地睡了。我却怎么也睡不着。我第一次发现,10度原来很冻脑袋!低温让我的头脑非常清醒,伴随着满屋淡淡的“一手店”的味道,我开始思索一个哲学问题:“为什么同样是脚,鸡的脚叫做鸡爪,而猪的脚叫做猪手?” 思考了良久也没有正得,眼看着快睡着了, 终于来电了!

电停了大约20个小时。后来知道我们算是很幸运的。因为有的人家停了60个小时的电。有了电家里终于暖和了一些。周三的早上一家人终于吃了热乎饭。不过很快电就又停了。更糟糕的是水压越来越低,自来水也快停了。心急的妈妈一边存水,一边不断地催促米粒和花花抓紧吃,然后趁着有水赶快去上厕所。此情此景,我就随手发了一个朋友圈:“趁着有电赶快吃,趁着有水赶快拉!” 没想到很快变成了朋友圈金句,被大家纷纷转发。这里我想和贾浅浅说一声,真正打动人的文学作品并不是基于屎尿屁,而是同理心。

无论发生什么,小朋友总是很快乐的,米粒,花花和邻居的孩子们一起高兴地玩雪。花花第一次在外面看见了冰溜子兴奋的大叫。我估计是她想起了电影《frozen》。学校的表现一贯的稳定:停课一个星期外加好几天。魔幻的一周对孩子来说真是福无双至。

周四开始,气温开始回升。电力供应也开始恢复正常。朋友圈又变得热闹了起来。讨论最多的就是如何修理水管。另外热度很高的一个消息就是一个外号叫“眉山剑客”的复旦教授陈平,在国内是反美斗士,曾经说过2000人民币在中国过的比3000美元在美国还幸福的话。这个剑客也不知道是手贱还是嘴贱,在自己微博上一不小心暴露了原来已经在德州定居了。因为也遭到的严寒带来的不便,又把自己以往的工作想起来了,开始批评美国政府:“美国军方还夸口要和中俄战争,一次寒潮就把美国近半沿海地区的电网和交通打残了。” 这种分裂特性貌似在国内非常流行,就像本来都说好了“没有什么救世主”,但偏偏“东方出现了一个什么东。” 我这里也不是批评复旦,我们工大虽然穷,但我们不去偷,不去抢,不跟别人打架,我们也不说谎话。 另外一个消息就是茅台的工程师入选了院士。 这也没什么大不了,任何东西都有科技在里面。例如:老婆饼里为啥没有老婆?十三香为啥不是十二香?不过眼瞅着华为因为芯片都被逼得快去养猪了,再整什么茅台院士真是不合时宜。回想起前一阵饶毅举报某位中科院院士论文实验不可重复的问题,其结论就是某位其他院士说:“生命科学的实验,有些就是不可以重复的!” 这下牛逼了!遇事不决,量子力学,质量不够,暗物质凑。生命实验,不可重复。目前科学三大谜题妥妥搞定,下一个诺奖稳了!中国有些科技领域还真是别人卡住你脖子,你就翻白眼,松开你脖子,你就吹牛逼!现在我明白为啥茅台出院士了。用本山大叔的话说,两瓶茅台下肚,话就没处听了。“我刚买了一个航空母舰,你们两口子赶快把鱼塘给我腾出来,我要抓紧时间训练!”

不过国内还是心地良善的人多。我的一个国内朋友就问我:为啥解放军还不出动去救灾?我赶快去看了看新闻,还以为像陈平教授说的,解放军打过来了。还有朋友看到国内的宣传《美国先是几千万人染病,没有染病的幸存者纷纷在饥寒中冻毙》后,非常贴心地给我邮来了200美元。借用现在流行的一句话就是:帮助性不大,但是温暖性极强。虽然200美元现在已经雇不到水暖工了,但是我依然感到那份足足的温暖。

瑾以此文纪念过去的魔幻的一周,感谢还惦记我们的国内的朋友,感谢另外两位邻居,感谢我的家人。百年一遇的飓风,百年一遇的病毒,百年一遇的严寒。做人何惧艰险,不妨一地肝胆,我真的还想再活五百年!

臀姐和举哥

———- 赵岩

首先解释一下这个标题,省的你看之前四处找纸巾。这个标题来源于两个最近真实的新闻事件。第一个是清华的学姐感觉学弟在食堂摸了她的屁股,学名叫臀部。这本来是个小事,她这个年龄,我这个岁数,岁月青葱!再说学弟也当场解释了是书包碰到的,不是自己去摸的。不过后来这个事情就有点变味了,学姐居然去贴了一张大字报,把学弟的个人信息公布了出来,还说学弟性骚扰了她。闹到这个程度,多少有点令人不好理解了。后来的高潮部分是根据监控录像,学姐的臀部确实是学弟的书包撞到的。学姐这个时候居然没有抱歉,还说了一大堆不知所云的话:其中心思想就是:如果你摸我的臀部,那你就是禽兽。如果你不摸我的臀部,那你禽兽不如!这里我暂时叫她臀姐了。第二个是哈工大的一个学生,感恩节的时候宿管阿姨准备了糖果,准备给同学们发一下。我们这个同学说这位阿姨宣扬西方宗教节日,要向学校“有关部门”举报。你也知道,在中国,“有关部门”是相当牛逼的一个部门,吓得宿管阿姨连忙道歉。这里我暂时叫他举哥吧!

为啥写这个文章?多少我和这两个学校还有些渊源。高考的时候不喜欢清华,所以上了本地的一所二本学校,主攻“凡尔赛文学”。 然后考研到哈工大,一直到博士毕业。期间还在哈工大当过两年老师。可以说工大是我半个母校。母校就是这个地方,随便自己怎么骂,别人骂一句都不行!所以我感觉有必要骂两句。工大的官方反应就是铁锹飞舞,和得一手好稀泥。毕竟“政治正确”这种事,在全球都是主流。官方的态度我能理解。好在没有为难宿管阿姨,这没有让我失望。如果连这个底线都没有坚持住,那我们哈工大和清华,北大还有什么区别!

举哥这个人,仔细想想挺有意思。平时都是不举的,不知道怎么就在感恩节这个日子举了。难道是“近节情更怯”,还是“睹物更思人”?要分析举不举这个问题,我们必须从根上找原因。下面一起分析分析。 举报这个事,其实是不齿的,尤其是在需要兼容并包的高校里面。但是前一阵,全国高校居然鼓励学校举报老师的“政治不当言论”,所以这个举报就上升到了党性的高度。这就是为什么就连一向“功夫到家”的哈工大也吓得只和稀泥的缘故。下面,让我们从党性这条线索挖一挖。 其实大家都知道。党性,忠诚和智慧。这三样东西你最多只能占两样。也许,我也希望我们的举哥有党性,有忠诚。如果是这样,没关系。人会长大,他还需要更多的阅历和历练。智慧这东西从来都和苦难在人生的路上结伴而行。我们需要做的就是给举哥更多的时间。工大的官微宣扬我们反对宗教进校园,如果这样的话,可不可以也反对政治进校园。 年轻人,热情,又容易被人煽动。他们总认为目的正确,手段就可以非法。 但是目的正确这个事,是随着时间会改变的,而手段非法,会一直非法下去,直到侵害到每个人,包括自身。记得文革的时候,发生过一个红卫兵举报他的妈妈,然后妈妈因为他的举报被枪决的事。当初无比光荣,伟大,正确的目的现在看来变成了笑话,但是自己的亲生母亲却被自己送入了地狱。我不知道这个红卫兵长大后如何反思和面对这样的人伦惨剧。每当念及此处,我都脊背发凉。

最糟糕的一种情况是举哥有党性,还有智慧,这才是最可怕的。这种人都是精致的利己主义,为了一己私利,任何人都是他可以利用的工具。这种人左八荣,右八耻。三个表儿腕上戴,核心价值观纹胸口。哪怕对别人的伤害有多大,他们都满嘴政治,毫无怜悯。这种人欺下媚上。对付举哥这种人,你只要告诉他:我们的长公主念哈佛的时候也过洋节!只要这一句,就可以吓得他终身不举了。这种人其实最为祸国殃民,整个的社会风气都被这种人败坏的一干二净。如果是这样,那么我们的臀姐和举哥,小小年纪就有这种手段,这种心思。该不该我们对中国的“军备竞赛式”教育和“踩踏式”竞争以及我们的“举报文化”进行一下整体的反思呢?

有些人不服气,你说党性,忠诚和智慧,每个人最多占两样,难道没有特例?当然有了!比如李毅,他只占了一样:党性。 这个人早早移民到美国,都拿了绿卡。所以忠诚是谈不上。最近被邀请回国去赞扬中国的抗疫成就,说白了就是奉旨叼盘。也许国内的媒体对胡总编的吊盘样式感到有点厌倦。需要一点从国外来的新鲜感。但是很明显,在姿势上已经很难有突破,所以李毅只能在嘴上多下点功夫。为了花样出新,居然说出了“死4000人就相当于一个人都没死”这种混账话,而且说的时候满面笑颜。真所谓:谈笑间,四千灰飞烟灭。被新京报点名批评后,又叫嚣“党,政府和军队”会收拾你新京报的。岂不知新京报的老板是党刊:《光明日报》。这种毫无底线的乱叼一气,就连党内的人都看不下去。林林总总,明显说明李毅智商不在线。也不知道是谁邀请这么一位,也不知道栓个狗绳。难道不知道“遛狗不牵,全家新冠”的道理吗?不过全家新冠没关系,李毅会给你清零的。

说了这么多,其实我最希望的一种情况是这样:我们的臀姐和举哥有一次要去男寝室,但是被宿管阿姨挡住了。然后臀姐的臀部就变得很敏感,被人一碰自己就忍不住快乐地喊了出来,哪怕只是被一个硬硬的书包撞到。而举哥怀恨在心,一定要找个借口整治一下宿管阿姨。这样下次再领臀姐进寝室,阿姨就不会管自己的。这个故事听起来不那么高尚,但是它温馨。它让你感到:年轻真好,爱情真好,当个正常人真好!它证明了不仅你东京热,我们哈尔滨更热!如果我的猜想错了,那我不仅没有感觉到热,于此相反,心头升起一股彻骨之寒!

美国大选白痴指南

—— 赵岩

写这篇文章的原因有二:其一是不知什么原因,家里突然断网了。而且一断就断了一整天。以前就一直想写点选举的事,可是一直日理万机。今天突然发现万机都连不上网,于是就都不想理了。剩下的时间很无聊,那就写点多西吧。冥冥之中,也许是天意吧!其二也是想蹭热点。本来想蹭李雪琴来的,发现蹭来蹭去,连个毛都蹭不上,还是算了。眼看着热点越来越少,如果大选这个热点再蹭不上,再过几天也要过去了。

切入正题,美国大选到底应该怎么选?首先建议大家,美国大选不要根据候选人的政策选。为啥呢?因为这些政策你真懂吗?多收富人税对经济好还是不好?你能说的清吗?奥巴马保险有多少条款,你都研究过去吗?不知你怎么样,反正我是说不清。不仅我说不清,我相信很多经济学家都是各持一词。其实很多社会问题,人都是无知的。如果不仅不承认自己的无知,还要靠着自己力大硬来,那就是无耻了。下面举几个真实的社会问题的例子:一个最经典的例子就是经济学家和环境学家打赌。环境学家说人类过度使用金属,所以金属会越来越贵。而经济学家说不仅不过度,还应该更多使用,这样金属价格就会下降。最后两个人以十年为期打了个赌。赌局的结果反倒是经济学家赢了。从这个例子来看,有些社会问题不仅复杂,而且还有些反直觉。其实如果想通了道理也不难。就像中国现在的粮食问题,只要大家玩命吃,粮食价格一旦上去了,政府自然就会把房子扒掉而去种水稻, 而不会像现在这样把水稻拔掉去盖房子了。 另外一个经典的社会问题就是:“某某东西会抢走了我们的工作”。 可能你不知道,这种论调从100年前就出现了,而且每过一段时间就会出现一次。最早的结论就是机器会抢走我们的工作,然后是流水线会抢,电脑会抢,人工智能会抢,机器人会抢,最新最时髦的观点是中国人会抢。基本上每隔26天左右,这种观点就会来这么一波,特别的准时!不过现实就是每次被抢走的工作都被产业升级创造出来的更多的工作所弥补了。从这两个例子看,不要指望着什么聪明的政策能够解决复杂的人类社会问题。不仅如此,还要特别提防那些巧克力味的政策,因为一旦你吃到嘴里你会发现那完全是另外一个东西,比如人类社会中最完美的设计和政策:某某主义。

其次建议大家,美国大选不能根据媒体报道选。 对中国人来说,不要根据朋友圈的消息选。 现在所有的社交媒体,其实都在分析你的喜好,然后给你发一些你爱读的文章,这种现象就叫做“回音壁”。这种现象会不断加深你对某件事情的刻板和偏差的印象。 这早已是行业内的明规则—“如果你不买商品,那你就是商品”。 就算你试图去读各种不同意见的文章,你也会发现现在的各种媒体其实都是扭曲的。比如前几天我看到关于女性堕胎,反对派说要“利用纳税人的钱去给人非法堕胎。”这我绝对不能忍,如果这事是因为我,那我出份钱也就认了。但确实不是我干的啊!而支持派却说反对派是“剥夺妇女的自由意志”,这都什么年代了,妇女自由一定要捍卫,这我也不能忍。 静下心来想想,其实都是个人的事,而且case by case。媒体都在刻意扭曲和政治化。就像前几天看到中文媒体报道:新人新婚之夜抄党章。看完我就非常气愤,新婚之夜,难道不明白“党指挥枪”这么深刻的道理吗?

既然不能根据政策选,也不能根据媒体选, 难道让我们根据颜值选吗?那我们实在投不下去啊!这我理解。这就是我这部白痴指南横空出世的原因。现在我来公布答案:那就是要根据自己选!说买了就是你对现在的生活满意吗?满意就投现任的,不满意就换一个。就这么简单!

有些人也不知道自己的生活是否满意?好吧,白痴指南就是为这些人量身定制的。我好人做到底,送佛送到西。如果你不知道自己的生活是否满意,那就选择换一个。毕竟新官上任三把火。选举说到底并不是一群羊要选一个高富美且伟光正的牧羊姑娘,而是在两头狼中做个选择。 选中的可以吃羊,选不中的要上一边挨饿。对羊来说,确保自身利益最大化的关键就两点:第一不要让任何一头狼饿死。第二就是定期把一头狼送去挨饿。其中的道理你细细品。如果想明白了,你就会认同我最后一句话:我讨厌总统,但是支持总统制。与此同时,我热爱主席,但是反对主席制。如果要给反对加一个期限的话,我希望是终身。

中美政治疫情之我见

——–赵岩

这篇文章政治很不正确, 人性很不光明。不过这正是我的目的,所以你不用谨慎观看。谢谢!

最近疫情的原因,和国内的朋友和家人的联系增加了很多。他们对我关心的同时,对美国政府和总统表达了 强烈的愤怒和严厉的指责。再次印证了那个古老的传言:美国人总吹牛自己敢骂自己的总统,中国人说这算个屁, 我也敢骂美国的总统。

指责的两条最主要的原因是:1)特朗普总统很傻。2)美国政府完全不关心民众的死活。这两个指责其实我真没有办法辩驳,因为说的很对,没毛病。不过回头想想,总统并不是一个典型的政客,这一点你从他叫金正恩“火箭人”就能看出来。 整个过程就像两个孩子吵架,三胖子说我有了一个新玩具,boom~;唐纳德▪狗剩子说我桌上有个更大的,boom~,boom~。 既然不能把很傻这个标签拿掉,我希望再加上一个标签。那就是: 总统很傻,很天真!希望把画风多少扭转一下。

不过总统真的很傻吗?也未必。就算总统很傻,别忘了还有一套行政班子。他们个个都很傻?这个多少有点说不过去,上那一下找那么多傻子?坦白地说,总统的核心职责并不是让你夸他聪明,他的核心目的就是连任。正如我们的雍正帝的“为君难”。当总统,当皇上,当主席,这都是天下第一苦差,所以谁也别和我抢,谁和我抢我就跟谁急! 既然要连任,那就是要花最少的代价,解决最大的问题。有了这一基本指导原则,我们来分析一下处理疫情的策略。说到底无非就三种:1)都关起来,2)都放出去,3)隔离患病的,其他的继续正常生活。第3种方法看起来很美,但是执行起来成本太大,所以可以忽略。中国目前执行的就是都关起来的策略,而美国就是采取都放出去的缺略。 为什么呢?这里面有一些很复杂的原因,我这里就不一一展开细说了。每个国家有自己的具体国情,例如:美国总统的权限,对疫情的认识,最大多数的民意等等。对于一个居家工作的程序员,都关起来是最好的策略。但是对于广大的服务行业从业人员,都放出来才是最优选项。 而作为总统,他只关心收获最大的选票。 至于服务行业人员多还是程序员多,他比你清楚。 美国现在推行小学9月份开学,我是坚决反对的。我孩子冰雪聪明,文曲星下凡,根本不用上什么学。至于为啥学校成绩还一般,我现在强烈怀疑是不是学校给我们教傻了?我曾和我孩子朋友的家长聊天,他反倒支持美国9月份开学, 他的忧虑就是一年不上学对孩子危害更大。每个人对某件事的看法一定会有不同,这是很正常的事。总统的核心目的不是让你去选他,而是让最多的人去选他。所以不要总说总统傻。你笑总统太疯癫,总统还笑你看不穿呢!

至于美国政府不关心民众死活, 我个我真承认。 不过我想说句让人丧气的话:没有一个政府真地关心你的死活。真的!这不是我说的。Rush Limbaugh说:There’s nobody who cares more about you than you, and there’s nobody better equipped to take care of you than you. 里根说:government is not the solution to our problem; government is the problem。毛泽东说:“为人民服务”。 所有这些伟大的政治家都很明晰地阐述了政府的职能:政府只想维持整个社会的运转,然后它好收税。至于个人的生死,对不起,我不care。他死了,关你屁事。你死了,关我屁事。只要大多数人活着就行,这样我就可以继续收税。对了,顺便说一句:7月15号是交税截至日。

我现在最担心的问题是:我们现在面临的问题除了疫情,还有偏见和撕裂。这种偏见来源于两个方面:廉价的碎片化信息和同质的内容。廉价的碎片化的信息无处不在,不请自来。这种廉价使得你不愿意思考,不愿意把很多信息综合起来分析。 同质的内容又使得你很容易得到一个结论,同质的内容又不断地,反复地强化你这种结论。在中国你听不到不同的声音,在美国,虽然有不同的声音,但是大部分人选择性的过滤。这种同质的内容使得你很容易得到偏激的结论,一旦结论偏激,带来的就是对不同意见的容忍度降低,带来的就是对整个社会的撕裂。整个的过程中,偏激总是淘汰理性,愤怒总是阻止思考。这一点才是我们最需要防范的。

我来美国之前,对美国的枪支问题的认识来自于新闻联播。那就是:资本家为了利润,花高价贿赂政客,不让他们禁枪,从而造成无辜美国人民的伤亡。到美国之后,开始接触到不同的声音:“Guns Don’t Kill People, People Do”, 开始了解宪法第二修正案对权力约束的努力。开始产生问题:如果一个社会的好人大于50%,那么集体拥枪会提高犯罪率还是会降低犯罪率?最关键的是知道了美国社会大于60%的人支持拥枪(包括我自己)。 这些人不会上街游行,不会喊口号,他们只做一件事,那就是去投票。如果你是奥巴马,那最好的策略就是:为枪击案中遇害的人流眼泪,然后擦干眼泪。以便下次继续流。

所以我的建议就是:以后无论听到什么声音,先去找找看看不同的声音怎么说。然后把两种不同的声音综合起来,开始自己独立的思考。真相是什么, 也许永远不知道,也许不重要。理解了人性,理解了历史,你自己开始不惑,开始释然。最后就连帝哥都为你高兴,这就是为什么:人类一思考,上帝就发笑。永远不要和别人争论政治话题。 你要切记一点:吵架这种事,最后总是傻逼赢。一旦有了自己的结论,那就用自己手里的选票偷偷表达一下就好。如果没有选票也没关系。在“我们不惜一切代价”的文化里,如果你很幸运地恰好是“我们”,那就一起跟着庄严地喊口号。如果一部小心倒霉变成了“代价”,也别忘了喊声:“好快的刀啊!”

我的女朋友漏电了–论C++中的失败(failure),缺陷(bug)和异常(exception)

—–赵岩

先做个广告置入,如果喜欢这篇文章,你可以到 zhaoyan.website/blog 去查看于此类似的C/C++文章。

我承认有点标题党了,不过这真的是一篇写软件的文章,所以如果你已经抽出了一张面巾纸,那么趁早再把它完美的放回去。这篇软件文章很软,源代码不多,而且大部分都是伪代码。所以很适合所有人看。我特别推荐年轻的初学者,把纸巾放回去后,继续看下去。如果把这几个概念理清楚,对未来的工作非常有帮助。

先说失败(failure)。常见的软件的失败主要分为三种,编译失败,运行失败,结果失败。下面通过一个程序的例子来说明:
—————————————
int l1 = 0;
ll= 23; //compile fail
int a[10];
a[10] = l1; //run time fail
if(l1=1){
… // result fail.
}
——————————————
你可以把编译失败理解成世界上最聪明的一堆程序员在帮你审核你的代码。如果审核失败,他们会提醒你在造成严重错误之前修改你的问题。单从这一点看,C++也比一些解释性的语言更加安全。例如源码中你想把变量l1修改一下。但是由于笔误写成了ll。C++的编译器会对你大吼的。但是Python一声也不吭,看你运行时候的笑话。

运行失败是指你已经有了可执行程序,当你运行它的时候,它crash了,挂了,死了,kick the bucket了。一个你最经常看见的输出就是访问越界内存带来的Segmentation Fault。所以请记住:男人头,女人腰,还有越界内存。这三样东西永远不要摸,否则会挂掉。

比起结果失败,你会发现运行失败是多么美好的一件事。上面的if语句是C++中非常著名的一个结果失败的例子。你的程序编译正常,运行正常,就是结果不对。而且你还不知道原因所在。另外一个常见的原因就是C++的数值计算溢出。这在C++中是非常著名的“未定义”。 C++标准委员会乐观的认为如果我“未定义”,那么天才的编译器程序员会发明一种最聪明的方法来解决这个问题。但是实际的情况是:天才的编译器程序员是最懒惰的程序员!如果你“未定义”,那么他们就什么也不干。结果就是到底发生什么,鬼知道!!!

如果你还没看懂,没关系!我再给你举了生活的例子。一个程序员根本就没有女朋友。这个就类似于编译时失败。一个程序员有个女朋友,但是他第一天就去摸人家女孩子的腰。有些地方还真是和尚摸得,你却摸不得,结果女朋友愤然离去,这可以对应运行时错误。最后一种情况当然就是结果错误了。程序员有了一个漂亮的女朋友,第一天他去摸人家的腰,女孩子不仅没有生气,还娇羞的把头靠在程序员的肩旁上,对他说:“讨厌了,你刚才把人家摸怀孕了啦!”

现在的问题就是:如何能尽早的发现女朋友是否环孕?不对,我说错了,我是想说如何能够更早的发现失败?

首先,常见的失败的原因主要有两种,一种是缺陷(bug),另外一种是异常(exception, error)。好多人搞不清楚,所以我上段代码:
———————————
String key = name;
If(key = “Yan”){
handle = openFile(key);
salary = handle.read();
}
———————————
请注意,我说的缺陷就是bug的含义。也就是常说的虫子。缺陷主要有两种,设计缺陷和实现缺陷。上面的代码中,用名字来做为查找键值就是一种明显的设计缺陷。如果你在办公室喊一声“狗蛋”,会有好多同事答应的。另外一种实现缺陷我们上面介绍过,key==“Yan”写成了“key=Yan”。这些都会造成程序的失败。
那么我们如何能尽早的发现bug呢?一个很牛逼的工具就是unit test。不过C++也提供另外一个有力的工具,那就是assert。Assert用法是最简单的。下面我们看看它是如何发现我们的缺陷的。
—————————————
PrintSalary(string name, int salary){
Assert(name == “Yan” && salary<=200,000);
}
————————————-

Assert的基本理念可以应对成一句话:“反常必有妖”。上面这段代码的含义是如果发现Yan赚的钱超过了20万,那程序一定是哪里出现问题了。就像你发现你的名字出现在福布斯富豪榜上了,那富豪榜一定是印错了。你最好复查一下计算薪水的代码。如果你再阅读一下上面计算salary的代码,你就会发现是重名的设计缺陷和if的实现缺陷才让Yan的salary那么多。

关于assert,有三点需要说明,第一就是在哪里放assert和用assert验证什么? 一个基本的原则就是函数的各种输入和输出值,但是具体上则完全是根据具体的问题和程序员的经验。你可以把assert想象成传感器。要检验一辆车的质量,一个有经验的工程师知道哪里需要放传感器,用传感器检测什么。而工程师的女朋友会发现:这个颜色我不喜欢!

关于assert 的第二点就是在程序发布版中,assert是失效的。这个很好理解。一个车出厂前才需要用各种传感器来发现各种缺陷,一但发现缺陷,就应该找到缺陷的原因并修正它,直到零缺陷,你才应该把车出厂去买才对。如果用户去买车,发现车上都是传感器。这会影响用户的速度和体验;同时就算是用户发现车漏油这个问题,你让普通用户做什么呢?所以说assert只是工程师用于产品出厂前检测缺陷的,而不是用于最终产品的。当然,零缺陷只是传说,所以车厂有召回,软件公司有补丁。

Assert的第三点是现在c++支持static _assert了。它可以在编译的时候就发现问题,这印证了我前面说过的,编译失败要好于运行失败。

———————————–Static_assert(sizeof(int)>2)
————————————–

现在我们回来看看计算薪水的代码,看看OpenFile这一段,这里并不是缺陷,而是会发生异常。文件打不开有太多原因,权限不够,名字不对,别人给删掉了。所有这些原因都不是我们的程序能完全控制的。虽然这造成了程序的失败,但是这既不是设计缺陷,也不是实现缺陷,而是一种异常。

对于异常,我们应该用try catch捕捉到这种异常,然后根据上下文做出正确的反应。常见的异常多发生在IO,网络链接和用户交互上。它和assert一样,如果要用好很大程度上取决于具体的问题和程序员的经验。与assert不同的是,它也必须存在于产品的发布版本中。在车的例子中, 车漏油是缺陷,但是车胎扎了就是一种异常。在平时使用的时候,你也应该时刻准备着捕捉到这种异常,并做出正确的反应。这是它和缺陷最大的不一样的地方。
—————————————–
Assert(car doesn’t leak);

Try{
Drive on a nail;
}
Catch(“flat tire”){
Replace spare tire;
}
——————————————–
没看懂吗?没关系。让我们继续程序员和女朋友的故事吧。我们上文提到的程序员自从把女朋友摸环孕后有点怕怕了。他决定从日本买一个型号为“苍井-玛丽亚”的女朋友。拿到货后很快发现女朋友不仅漏气,而且漏电!漏气这个事,明显属于异常范围的。因为即使正常的使用,漏气也会正常地发生。所以一般厂家会提供一些胶水来应对这种情况。

但是漏电这明显是一个bug,厂家是应该用传感器在出厂前就发现并修正这一问题的。也就是说厂家是不应该把这一产品出厂来买的。你需要向厂家报告这一缺陷(bug)。正常的情况下,你会收到厂家的教科书式的回复:“It’s not a bug, It’s a feature!”。我还真的承认。女朋友漏电这个事,人家说是feature(特性),Make sense(没毛病)!

异常是一个很大的topic,感兴趣的可以查看我的两本书《C语言点滴》《Drop of knowledge of C++》 。里面有很多的关于异常的介绍。

从程序员和女朋友的故事中,我希望你能记住以下几点:
1)编译失败好于运行失败,运行失败好于结果失败。
2)失败通常由缺陷和异常造成。
3)对于缺陷(bug),用assert和单元测试在开发期间尽早发现并修正。
4)对于异常(exception),用try catch 在产品使用的全程捕捉并处理。
5)程序员就不应该找女朋友!

C++模版从精通到精神分裂

by – 赵岩

先做个广告置入,如果喜欢这篇文章,你可以到zhaoyan.website/blog 去查看于此类似的C/C++文章。
这是一篇写软件的文章,但是很硬,提前预警一下,女生不要看!

所有写C++的文章,如果没有源代码都是在耍流氓。闲话不说, May the source be with you!

https://gist.github.com/zhaoyan/5d39a57e64f2929a95704c6253d35530

这是一个教科书般经典的例子。介绍C++的继承和多态。 这里唯一需要重点强调的是:对函数LetAnimalTalk和vector<Animal*> va 来说,我们可以想象他们是客户。通过继承把变化封装到基类的后面,这样使用基类接口的客户就不需要改动! 对客户来说,无论基类后面怎么变化,你都影响不到我。例如,如果现在有一个经理狗加入了项目团队,你的LetAnimalTalk函数是不需要任何改变的。

So far so good! 现在看看引入模版后,发生了什么?

https://gist.github.com/zhaoyan/c5acca04eda622984f2288c06eb7b0a2

基本的应用场景是这样的。对于animal, 你可以用字符串来表示他的ID, 如果你想developer是不应该享有字符串名字的,那么你也可以用整型数来表示他的ID。上面整个的程序,如果你把main中换成下面的样子,除了猫会有点意见,其它一切都没有问题!

https://gist.github.com/zhaoyan/d728d68b5b976d053fa47a310f076768

上面模版继承的一个最明显的弊端是语法变得更加臃肿和复杂了。例如,你不可能在子类中直接引用基类的变量了。如果你引用他,必须使用Animal<T>::id_这样的语法。背后的原因是当编译器编译Cat的时候,Animal是根本不存在的!具体的细节你可以看后面的参考文献1。

另外的一个问题就是虚函数的效率问题。这种多态是发生在程序运行的时候,主要通过虚函数表进行调用分发。所以有一定的效率损失。下面我们再看另外一个例子。 由于猫不喜欢整型ID的名字,所以我们这里完全去掉这个feature,重点关注如何利用模版实现多态。
https://gist.github.com/zhaoyan/c429cb015c556ad23a103a2068540fa6

这段代码中,有几点注意一下:
1) 多态已经不需要用指针了,我们可以用引用来支持多态。

2)在函数LetAnimalTalk中,pa.talk()到底调用那一个 talkImplement是在编译的时候就决定了。这是一种静态多态技术。所以没有效率的损失。

3)但是函数LetAnimalTalk现在必须是模版函数,同时我们也失去了用vector同时保存Cat 和Developer的能力。这是效率提升带来的灵活性的损失!

4)这个是CRTP模式,更多介绍看参考文献2。翻译过来就是“好奇地不断追问自己!”这应该是一种精神分裂的明显的初期症状了。

目前为止,我们介绍了三个例子。还都遵循着一个基本的IS-A的逻辑关系。也就是说,Cat是一个Animal,Developer也是一个Animal。下面介绍三个IMPLEMENT-BY的逻辑关系。第一个例子完全没有使用继承。

https://gist.github.com/zhaoyan/5d6f49d35aada5ff851e79244a5cd6e1

1) 这段程序中,通过模版参数,在编译的时候就把不同的talk行为的实现方式传递给Animal类。这个方法在STL中运用的相当广泛。具体的例子像STL中的map类 template<
class Key,
    class T,
    class Compare = std::less,
    class Allocator = std::allocator<std::pair >
> class map
其中, std::less就类似于我们上面的SayMiao。

2)由于导入的是某种行为,所有再叫做Cat就不合适了,所以这里把类的名字叫做SayMiao

为了实现IMPLEMENT-BY关系,我们也可以使用私有继承:

https://gist.github.com/zhaoyan/c7c02387f4b0867991ad837ae4fb82e7

1) 有没有被
template< typename T>
class Animal: private T{
这样的语法惊到!没关系,我们慢慢来。首先私有继承不是IS-A的关系。而是IMPLEMENT-BY的关系。关于什么时候使用私有继承,什么时候使用组合(composition)
。请看参考文献3。

2)这种方式是Parameterised inheritance, 也是一种常见的设计模式,请看参考文献4

OK,最后的问题是,既然私有继承可以,共有继承行不行?在一个分裂的病人眼中,没啥是不行的!

https://gist.github.com/zhaoyan/07a8c0eb8dadd6eb1e3c7372ef2629a8

1) 这就是在Modern C++ design中提到的Policy-Based design。一个小提示是:现在在Animal中已经不需要talk这个函数了

上面我一共给出了六个程序。到 https://www.onlinegdb.com/ 把这六段代码拷贝进去,根据自己的理解和问题修改一下。“纸上得来终觉浅,绝知此事要运行”。 这其实是陆游给广大程序猿的一句忠告。 如果你有足够的耐心,你可以慢慢地深入的体会,这里好玩的东西还挺多的。由于篇章关系(主要是再展开我也不会了!)我就不多说了。

以上这六段程序,分别代表着六种不同的语法方式,表达出两种最基本的设计模式 IS-A还是IMPLEMENT-BY。首先,没有什么优劣之分,在不同的场景下,各有优缺点。另外,C++的模版完全不同于传统的C++编程。他的语法和想表达的语义有非常明显的分裂趋势,非常容易把传统的C++程序猿也搞分裂了。正所谓范型是C++最大的坑,但是不跳此坑,不足以谈人生!

如果没有看懂就算了!你完全可以说:“这个人已经疯了!” 这个我在标题中已经承认了!

参考文献

1)https://eli.thegreenplace.net/2012/02/06/dependent-name-lookup-for-c-templates

2)https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

3)https://isocpp.org/wiki/faq/private-inheritance#priv-inherit-vs-compos

4)https://blog.feabhas.com/2014/06/template-inheritance/

我眼中的某主义

本来最烦写这类文章的。大部分看客的心理我懂,无非就是觉得你愤青。真是贱人多矫情,废柴总愤青。只不过是最近国内某主义大火,我也想趁机蹭点热度,表一下衷心。还有一个原因,我不知道国内是不是真的还有人认为某主义无比正确,如果真的还有这种人,也让这些人听听平时他们根本听不到的不一样的声音。

要说某主义,那就要提另外一个大名鼎鼎的主义:法西斯主义。这两个主义还真是有一个很基础的共同点:那就是非常极端地看不上某一类人,而且一定要从肉体上消灭他们!法西斯是看不上某个民族的人,而某主义主要是看不上某个阶级的人。它没钱的时候,就看不上有钱人,它有钱了吧,又看不上没钱的人。从这点上看,某主义对本国人杀伤力更大。

要是看不上人这种事,本来就是人的天性。我小的时候就是看不上邻居的人孩子。他简直是我童年生活的噩梦。上初中就看不上我同桌,总感觉就是因为她,班花才总不理我。再大点看不上的人那就更多了,主要是看不上比我过的好的人。人啊!都这德性,这也没啥大惊小怪。

话说100年前,一个德国人,既然是人,当然也这德行!由于面包太贵有点吃不起,开始思考一些哲学上的问题。为啥我吃不起面包?想来想去,终于找到了原因,主要是面包店老板的原因。一块钱的面包,老板把5毛钱都拿走了。当老板就要赚钱,本来天经地义的事,知识分子却总爱剑走偏锋。面包店老板居然不是因为爱我才给我做面包,而是要赚我的钱。就是因为他赚我的钱,我都吃不起面包了!这不行,必须把面包店老板杀掉才行。这样面包立马就能便宜一半!

这个想法,YY一下也就算了。可是不巧的是,由于工业文明带来的产业升级,突然有很多人吃不起面包了。一些头脑灵光的人偶然看到这个德国人的某个主义,感觉如获至宝。原来可以通过这么高大上的一堆专业名词,干掉面包店老板,这样面包店里的面包就都归我了。太他妈好了。就这么干!

很快的,就是好几声的炮响,面包店老板人头落地。再然后就是他们突然发现,随着面包店老板被干掉后,一起消失的还有面包。这种复杂的经济学模型,我就不详细解释了!反正说了你也不懂。你要是还想不明白,你就问问自己:没有老板管你,你还干活吗? 不过最要命的还不是面包,要干掉面包店老板,首先要干掉两样东西,那就是民主和法律。

这个时候一些跟随者有些疑问了。面包不仅更贵,更差,而且更少了!!!但是非常遗憾的是,领头杀死面包店老板的一批人,现在已经有了不受法律和民主约束的权力,他们已经有面包,还有牛排,还有红酒,有军队。他们不允许你有任何疑问来动摇他的统治,随其自然的,运动开始了!

整人,杀人,贫穷和杀戮,这几乎和某主义如影相随。几乎就是老一套!苏联,中国,北朝鲜,柬埔寨,东德,波兰,越南….. 没有任何一个国家能逃脱出这个规律,好像被人划着圈圈诅咒着。

一切就这么简单,没有什么高深的道理可讲,一切都是从看不上某一类人开始,以贫穷和杀戮结局。人性细节处魔鬼丛生,这本没什么大不,但是不幸的是,某主义将人性的恶放大到最大!

要说千年最伟大的思想家,我只服卢梭:“人生来平等!”,排第二的是契诃夫:“大狗叫,也得让小狗叫”。排第二的主要原因就是他没有考虑中狗的感受。这才是解决社会问题的终极之道!至于其它的伟大的什么什么家,统统负分!