DDD与面向对象设计

DDD最近几年比较热,因为它在某种程度上确实解决了一些设计问题,它强调在软件设计的初期就引入领域专家,始终围绕着软件要解决的核心领域问题来展开,很好的避免了一些架构过度设计,过早的引入非领域问题设计,比如事务,并发。它围绕着几个核心概念——Entity, Value Object, Aggregation, Repository, Domain Service, Domain Event——来设计并实现功能,整个团队用一致的工程语言来描述和组织相关的代码,比较有效率,代码质量也有一定的保证。但是它也有其局限性,我用《面向对象分析与设计》的解密文本的应用来举例子,题目描述很简单,给定一个密文,设计一个解码程序。沿着DDD的思路很难去定义相关的Entity, Value Object, Service并展开相应的设计。对于这样的问题,面向对象程序设计给了一个非常漂亮的解答,它定义了一个黑板,有很多拥有某些知识的观察者在替换它,比如单字母在英语中,一般是I或者A;比如三个字母最后一个是E的很可能是THE;比如结尾连续两个字母一样很可能是EE …… 从中我们可以学习到面向对象设计如何针对一个很困难的问题,通过分解一个一个细粒度的对象,每个对象只解决一部分小的问题,最终各个对象通过交互,来得到最终的答案,所使用的 Blackboard模型 不在这里展开讲了,有兴趣的读者可以自行深入了解,非常推荐大家认真学习这个问题解决过程,能够很深切的体会到面向对象设计的思维。从中我们可以看出面向对象设计一般不关心数据,它关心的是一个问题在现实中如何被解决,并通过模拟这个解决过程,定义其中所牵涉到的对象,行为,以及交互,从这方面说,它与DDD是截然相反的,无论DDD如何强调要设计充血的Entity模型,它一开始都是围绕着数据,数据关系,以及数据变化来设计的。数据驱动的概念其实已经有很多年了,它确实是一种很有效并且高效的开发方式,很多年前,大师们就说

Get the data structures correct, and the code will write itself.

早期的C程序员解决问题,都是定义好相关的struct,详细到xxx字段是用int还是char,然后围绕struct来开发相关的方法并组织这些方法的调用过程。企业级应用的开发也是先做好数据库的设计,整个应用层是围绕着数据库来开发的。说到这里,想扯一点Go语言的语法设计,只是个人感觉,因为Go语言的设计者们都是资深的unix c程序员出身,貌似他们更认可数据结构的重要性,所以Go语言的面向对象的语法设计是有点别扭的,structure embedding,带接收者的方法定义,带来了继承,多态,覆写概念上的理解障碍。

既然谈到了数据驱动设计,就再多说几句Kubernetes和Spring Cloud,现在这两种是常见的微服务架构落地方案了,他们就有很大的不同,Kubernetes通过定义精妙的Pod,Deployment,Service,DaemonSet,Ingress, CustomResourceDefinition等数据结构,所有的核心组件通过监控etcd的数据变化来调整自身,在不增加复杂性的基础上提供了很好的扩展性。反观Spring Cloud,基于面向对象的设计,提供了一个又一个的抽象,架构的复杂性和扩展性形成了正相关的关系,导致Spring Cloud整体的复杂度越来越高。非常佩服Google的Kubernetes的整体架构设计理念以及定义核心数据结构的功力。

DDD是一种自底向上的设计,非常适合企业级产品的团队开发,因为企业级产品绝大多数都是围绕着明确的领域数据来实现功能,而且DDD有很多好的实践与模式帮助解决很多开发过程中常见的问题。

面向对象设计是一种自顶向下的设计,更普适一点,更难掌握一点,很难归纳出有明确的步骤指导解决某一类问题,正如《面向对象分析与设计》中说的一样,完成详细设计后,识别Entity也是其中的一步,不过是在相当靠后的设计阶段了。

DDD和面向对象设计都是核心领域建模的方法,要完成真正的产品,还有很多方面要考虑。在软件开发领域,还跟多年前的论断一样,没有银弹!

最后,安利两本书,《面向对象分析与设计》《实现领域驱动设计》

面向对象分析与设计   实现领域驱动设计

Mega Menu设计实现小结

Mega Menu作为一种导航UI元素,在越来越多的大型应用中得到使用,尤其是当功能模块页面很多,Mega Menu提供给用户更丰富的上下文信息,友好一致的导航体验。
它一般长这样子,

这样子,

这次的设计跟传统Mega Menu不太一样,它不是为一个单体应用设计Mega Menu,而是为了适应很多个原来独立的产品以suite的形式提供给客户的产品策略。当前实际的状态是提供单独的landing page,每个应用对应着一个链接,每个单独的产品都保持自己的导航菜单,同时添加返回landing page的菜单,造成了在应用之间切换的体验对客户很不友好。现在要求在suite层面上提供一个Mega Menu的实现,提供统一功能的UI界面,比如用户信息,搜索,登录,通知,同时允许各个应用可以一定程度上可以定制扩展Mega Menu的UI和功能,比如应用可以在指定区域,左,中,右去添加菜单项并设定相应的功能;可以提供自己的dropdown区域的UI实现;原来各个应用的菜单行为要保留,比如搜索,通知,在不同的应用上下文显示正确的搜索结果;最后,要求对各个应用的代码尽可能少的侵入,各个应用可以用最少的开发周期去完成集成。

1. 基于React开发

<MegaMenu>
    <MegaMenu.Item ... /> 
    <MegaMenu.Item ... />
</MegaMenu>

2. 通过设定webpack配置output.library = “MegaMenu” 打包暴露给老代码的api尽可能简单

<script type="text/javascript" src="MegaMenu.js"></script>
<script type="text/javascript">
  var megaMenu = new MegaMenu(items);
  megaMenu.render(domElement);
</script>

3. items的数据结构

var items = [
  { icon: 'fa fa-bars', id: 'Products', type:'pusher',items:[
      { text: 'ProductA', id: 'ProductAId' },
      { text: 'ProductB', id: 'ProductBId' }
  ] },
  { text: 'item1', id: '1' },
  { text: 'item2', id: '2' },
  { text: 'item3', id: '3' },
  {
    text: 'item4', id: '4', type: 'dropdown', items: [
      { text: 'item4-1', id: '4-1' },
      { text: 'item4-2', id: '4-2' },
      { text: 'item4-3', id: '4-3' },
      { text: 'item4-4', id: '4-4' },
      { text: 'item4-5', id: '4-5' }
    ]
  },
  { text: 'item5', id: '5' },
  { text: 'item6', id: 'customizeddropdown', type:'dropdown' },

  {
    text: 'User', type: 'dropdown', id:'user', items: [
      { text: 'Logout', id: 'logout' }
    ],
    location: 'right'
  },
  {
    text: 'Settings',
    id: 'settings',
    icon: 'fa fa-cogs',
    location: 'right'
  }
];

4. 通过事件来实现UI外观和行为的扩展定制化,event bus的实现可以直接用nodejs的events.EventEmitter

megaMenu.addListener(MegaMenu.Events.ITEM_RENDERING_EVENT, function(id,itemContainer){
  console.log('item id=' + id + ' is rendering in ' + itemContainer);
}); 

megaMenu.addListener(MegaMenu.Events.DROPDOW_EXPANDING_EVENT, function(id, dropDownContainer) {
  console.log('item id=' + id + ' is expanding in ' + dropDownContainer);
});

megaMenu.addListener(MegaMenu.Events.PUSHER_PUSHING_EVENT, function(id, pusherContainer) {
  console.log('item id=' + id + ' is pushing in ' + pusherContainer);
});

megaMenu.addListener(MegaMenu.Events.ITEM_CLICKED_EVENT, function(id) {
  console.log('item id=' + id + ' is clicked');
});

最后,因为我的日常工作是一个后端Java程序员,如果你是个前端架构师,希望能和你探讨更好的方案。

给自己的blog加持了https

拖了好久了,用的Let’s Encrypt方案。访问Cerbot,几步就好了,实在是很简单方便。有两点要注意下,因为我用的是apache,所以就apache来讲。

  1. 如果有多个VirtualHost的话,记得先检查下sites-available的default配置,确定每个VirtualHost都有正确的ServerName,否则会有点问题。
  2. 别忘了加crontab去renew证书。

说个react-redux生产环境追踪action,state流的黑魔法

用react,react-redux开发前端的时候,在开发环境,我们会enable redux dev tool extension,方便我们在线调试问题,redux这种单向数据流,状态驱动的好处就体现的淋漓尽致了。但是如果在生产环境,通常都会disable掉这样的浏览器扩展,一旦有了问题,我们很难在线去追踪action流和store的变化,有时候debug一个问题还挺痛苦的。这里讲个黑魔法,关键点就是在生产环境取得store对象的引用。当然可以在代码中直接在window对象上设置store对象,这是最简单的方法,但是这样等于把store对象直接暴露给全世界,一般开发过程都是不允许的。如果不设置全局的store对象,那我们能不能从React的$r对象上索引到store呢?打开页面,尝试在Chrome console直接输入 $r.store,它会报告store是undefined。

你需要额外的几个步骤,下面操作基于Chrome,React Developer Tools extension,不管是不是生产环境,React扩展总是可用的。

  1. 打开React Developer Tools extension,选中store节点

2. 这个时候在$r.store就是可用的了,打开console,输入,

3. 随便浏览下页面,每次store的变化都会在console里打印出来了,你还可以手工dispatch action。

4. 如果你还想把action流也打印出来,就需要加一个很小的reducer在你的code里面,像下面的actionStream.js一样。熟悉redux的话,这是很简单直接的事情,就是在redux上的store上加了一个状态去记住每一次reducer调用时候的action参数。

//actionStream.js
export default function actionStream(state, action) {
    return action;
}
...
//combineReducers.js
import action from 'actionStream.js';
... //import other reducers
export combineReducers({...,action,...});

然后,subscribe的语句变成下面就好,

$r.store.subscribe(()=>{
    var state=$r.store.getState();
    console.log(state.action);
    console.log(state)
});

消失的微信发送按钮

一件很有意思的小事,起因是老妈下午火急火燎的给我打电话说,她手机微信的发送按钮不见了,就是输完东西,那个绿色的发送的按钮,而且她到小米的售后那里,小米的工作人员操作了半天也没搞好,正准备给她卸了重装,问她微信密码,她记不清,打电话问我。说实话,刚接到电话的时候我也是发懵的——什么叫按钮不见了?至于密码,我确实也记不清了。出于一个程序员的职业敏感,我赶紧阻止了小米工作人员的卸载操作,忘记密码及重登录的一系列后续操作对于一个老人家来讲实在是过于复杂了。第一感觉这应该是个开关的设置,但是什么样的设置可以让如此重要的一个按钮消失呢?为什么自己和周围朋友从来没碰到过呢?

感谢万能的google,很快我用关键字——“微信 发送按钮”——搜到了结果,原来针对android系统,微信有一个额外的设置项用来决定是否使用键盘的回车键来发送信息还是独立的显示发送按钮,而对于苹果系统,是不存在这个选项的,发送按钮都是大大的直接显示在虚拟键盘上。在android系统里,这个设置的路径是——我->设置->聊天->用回车键发送消息。很快的帮老妈设置好,她的微信回来了。然而事情没有完,我这边电话刚挂,老妈又发微信过来,说她的手写功能现在是全屏,这样她要多点一下按钮收起虚拟键盘,才能点发送按钮,但是以前是半屏手写的时候可以很方便的点发送。再次祭出google,这次有点复杂,因为android是一个开放的系统,所以可以安装第三方的输入法,因此根据不同的输入法选择,设置路径也是不同,中间过程不再细说,大约用了一刻钟终于帮老妈恢复了原来的习惯设置,老妈非常高兴,我在电话里听到她有点自豪地跟小米的工作人员说,我儿子在上海,是个软件工程师。我在电话这头儿留汗了。

在移动互联网时代,对于智能手机来说,因为受制于屏幕的大小,以及只能用手指做交互,所以相较PC 端,UI/UX设计就成为软件开发过程中异常重要的一环。UI/UX是英文User Interface and User eXperience的缩写,中文叫用户界面及用户体验,简单以一个按钮为例,UI决定一个按钮长什么样,多大,什么颜色,圆角直角,UX决定这个按钮放在哪,用户怎么找到它,怎么摆弄它,是点啊,滑动,双击还是长按。“老吾老以及人之老“,作为程序员,以前我经常对UIUX设计师的交互方案多有抱怨,通过这件小事,以后会再友好点,经常以自己的认知来衡量UIUX的设计是不对的。

事情到这里就结束了,说起为什么微信会这样的设置,也是一桩有趣的故事,感兴趣的朋友可以自行搜索了解一下,以后说不定有机会在朋友的android手机上搞个小恶作剧也为未可知,估计不知道的人碰到这种情况第一时间都是有一点发懵的。

仅仅追求能够工作的代码是有害的

作为一个老程序员,我也曾经询问抑或被问,“好的代码是个什么样子的?”

回想刚刚参加工作的时候,受限于个人的经验与天赋,绞尽脑汁的写出只是可以正确工作的代码已然是一件不易的事情,虽然如此,每当完成任务后,内心仍然一直念念地要去把它写的更好,即使那时候还没有明白重构究竟是怎么一回事,就是凭着一股子热情一遍遍的重写,从变量名,从语句顺序,从结构安排,从括号与每一行代码的缩进,精益求精,每当看到差的代码,总想去把它写的更好。

随着经验与教训的增多,自己写的第一版代码的质量也逐渐提高,对于各种细节的考虑也愈加周到。然而,不知道从什么时候开始,却心安理得地接受了这样一种观点,

能够工作的代码就是好代码

再也不会费尽心思的对能够工作的代码进行重构,同时,有无数的前辈和同事以及自己找了各种理由来赞成这种观点,从软件工程理论,从开发进度限制,从人员水平高低,不一而足。慢慢地大家看到低质量的代码,第一反应是去为这段烂代码找各种借口,我们容忍越来越多的低质量代码,更可怕的是我们正在容忍它逐渐侵蚀什么是好代码的判断力。

这是有害的,至少程序员来说。写代码就是我们的手艺,这不过是我们在手艺慢慢成熟的同时,内心正在失去热爱,失去追求卓越的动力所找的借口。这一现象在各行各业是相当普遍的,从中等水平到优秀,从优秀到卓越,每一步都要比刚开始的时候付出多的多的努力,然而效果却并不来的那么明显,至少从行外人的视角看来。因为艰苦,所以每个人都容易原谅自己在这条路上的徘徊止步,何况对于程序员的行业来说,这是个相当不错的理由——毕竟代码在正常的工作,不是吗?然而对于其他专业行业来说——医生,律师,会计师,飞行员……——分毫的差距,便是生存与淘汰。

回想当初入行时,当别人问“程序员的工作有什么好的时候?”,我们自豪的回答,“与机器对话要简单的多……我喜欢这种掌控的感觉……代码从不说谎”。写代码,我们是专业的。我们看到优秀的代码,两眼放光,我们为自己写出的优秀代码鼓掌,我们赞赏自己的技艺。然而,日复一日,年复一年,如今我们如何成为了许多人口中的熟练工,我们为何不再精益求精地打磨自己的技艺,不再追求优秀。突然间,许多同仁们恐慌,焦虑,到处充斥着“程序员到了35岁该何去何从?”,“程序员到了40岁还继续写代码是不是一种悲哀?”,“35岁老程序员被裁该何去何从?”的话题——可怕吗?在我们心智应该在人生顶峰的时候,我们甚至失去了凭着这手本事安身立命的信心,这在许多其他专业行业是罕见的现象,你可能想象不到这也许是从多年以前我们接受那个看似无害的观点——能够工作的代码就是好代码——开始的。

我想到Linus在Ted访谈时,提到好的代码的时候那种热情与兴奋,这段代码是最普通的一段C代码,完成最普通的功能,甚至我们每一个人都写过很多遍——移除链表的一个节点。版本一是合格的,但希望对于版本二你能闻到卓越代码的味道,那么我们至少还能分辨在这个行当里,什么是优秀,我们距离优秀还有多远。知道了不足,我们便可以奋起追赶,去重拾攀登卓越的信心。

版本一

    void remove_list_entry(entry)
    {
        prev = NULL;
        walk = head;
        while (walk != entry){
            prev = walk;
            walk = walk->next;
        }
        if(!prev)
            head = entry->next;
        else
            prev->next = entry->next;
    }

版本二

    void remove_list_entry(entry)
    {
        indirect = &head;
        
        while ((*indirect) != entry)
            indirect = &(*indirect)->next;
        
        *indirect = entry->next
    }

一次中大型产品web前端的技术选型过程

背景:老产品的基于ExtJS的前端需要配合server端微服务改造重新实现。

先说个人观点,非常喜欢 Vue,也想在这次技术选型中选择它,然而发现要说服各种技术人员,从开发人员到管理层,都有不小的阻力——不是阻挠的阻力——是在对于几个框架都比较陌生的情况下一种选择困难。对于大型产品开发来说——上百人月的开发周期——管理层还是以规避风险为优先,Google和Facebook的背景对于这些久不在开发一线的决策者们还是有很大影响的。

开发人员的观点

1.  不大愿意学习新的框架,并不是排斥,只是热情不高,这部分人一般比较资深,然后在传统前端开发领域积累了非常多的经验,这些人其实已经认识到,框架本身并不能让真正难的事情变得简单,也不能帮助做出漂亮的UI和交互体验,他们也不会迷信Google和Facebook的背景,有些人才刚刚爬出GWT的大坑。对于这些开发者来说,在选用新技术决策中是中立的,vuejs,Angular 2,React,无论选哪个他们都不会太反对,vuejs对于这批人是有着很大吸引力的,因为vuejs本身渐进式改造的理念对于他们来说是很容易上手的,不需要构建工具链虽说不算什么优势,但是对于传统的开发者来说,习惯过渡得非常自然,引入vue.min.js 即可以开始尝试开发,老开发者是比较拥护的,作为比较,如果使用Angular 2,在没有angular-cli或者其他skeleton的情况下,“Hello Angular”要比“Hello Vue”要来的难得多!这不仅仅是步骤的繁琐,甚至,他必须先去学习一门新的语言Typescript,去配置好几个文件。

2.   赞成Angular 2的,这部分人中,大部分是在另一个产品开发的时候,在Angular 1上积累了经验,这部分人里面对于Angular 2的学习曲线和Typescript也是颇有微词的。一部分是原来server端的人员,他们对于前端开发没有太多的经验,他们非常喜欢Typescript的类型特性,这部分开发者其实对前端热情不高,也不会去研究框架的实现,也不会研究ES5,ES6究竟是如何一回事,他们喜欢的是读完Hero tutorial然后按照套路,能够比较快而迅速的完成任务。

3.  赞成React的,这部分人其实有很多是开源社区的强烈拥护者,他们热爱开源,喜欢学习新东西,喜欢整合各种开源的好的实践到开发中来,他们很多时候喜欢稍微激进的技术架构选型,我看到一个方案是React + GraphQL + nodejs + RethinkDB的全栈javascript实现。

管理层的观点

1.  vuejs不能选,尽管vuejs在开源社区已经有着不算小的影响力,但相比较于React和Angular来说,背景和影响力现在还是不能比的,管理层们不愿担当这种决策风险。

2. 不反对React,因为React的影响无论在社区还是实践中实在是太好了,国内国外,随处可见褒扬的评论文章。然后很多人就忽略了React实际上只是负责了展示层,React一般需要比较多的整合努力,需要资深人员来挑选其他的库来配合使用。

3. 倾向于Angular 2,是因为它是大而全的一站式框架,对于整体代码质量控制和团队协作开发过程非常有帮助。

在技术选型初期,做原型的过程中发现这么几个Angular的问题,

1. Angular 2 deprecated了replace的选项,导致angular组件的Host元素始终显示在dom树上,绝大多数情况问题不大,但有时候会造成布局的破坏,比如有时候通过height/min-height:100%来控制高度,因为中间selector的存在导致布局的破坏,虽然可以添加@HostBinding去设置host元素的CSS属性,但这无疑是额外的负担。而且在开发工具上看DOM树总是有点奇怪的,中间插入了层层的各种selector元素。在开发中不得已开发了removeHost的attribute directive。

2. Angular 2如何方便与传统的js库一起合作,因为Angular 2从设计上就是排他的,对于企图渐进的逐步改造老系统的开发,有着比较大的影响。实际开发中有一点比较奇怪,我们的module bootstrap component是MainLayoutComponent,它有着下面的template,

 <main-layout>
    <app-header></app-header>
    <app-navigator></app-navigator>
    <route-outlet></route-outlet>
    <app-sidebar></app-sidebar>
    <app-footer></app-footer>
</main-layout>

这些组件模板的定义,我是从adminlte中抽取出来的。对于vuejs,我像传统的使用一样直接在页面引入adminlte.css和adminlte.js,没有任何问题,但是对于angular2来说我还必须把布局的初始化过程从adminlte.js里面抽取出来,写在MainLayoutComponent组件的onViewInited回调方法里,因为如果直接在页面引入,当初始化执行的时候component的template并没有开始编译和mount到dom树上,adminlte的初始化的部分代码是无效的,比如如果某些事件绑定不是基于全局捕捉的模式就会失效,

   $('#toggleBtn').click(function(){
   });

只有改写成下面的形式才是有效的,

   $(document).click('#toogleBtn',function(){
   });

Angular 2和vuejs的模板绑定到dom的时机应该是不同的,这可能会影响到一些前端库的使用。

3. 如何方便的从UI定义的元数据中compose组件的template,这一点vuejs同样也不能解决,因为他们都不是基于string template的,Angular 4的 NgComponentOutlet有可能帮助一点点。

4. 前几天演示研究原型的时候,好几个开发问我关于Angular 2的组件定义,在export class … 前面加个@Component(…)的函数调用是什么意思。

 @Component(...) export class XxxComponent

想讲清楚这个,又牵涉到ES6的DecoratorDecorator Factory,感觉Angualr 2使用的语法比较的超前,大家过渡得很不适应。当然如果是照着Hero的tutorial大家一般都没有问题,然而如果是规划一个产品的road map,那么必须要有人对其研究的非常透彻来应对未来可能出现的各种技术难题。

目前看来,基本决定使用Angular 2了,对于大型产品的跨团队开发来说,有巨头背书的一站式的解决方案是有决定性影响的,有点遗憾,这次没有说服其他人来选择Vue,还是自己的影响力不够。

Laravel 5.3前端ajax请求,后端丢失session的问题

微信的h5产品,使用React+Laravel 5.3,一个奇怪的现象是只有正常的http请求,在server端可以得到session数据,所有的fetch api调用都无法得到session数据,导致所有的api调用返回401,需要授权。最初后端认为是前端在fetch调用的时候没有加上credentials参数,导致laravel_session cookie没有发送。

credentials:'same-origin'

后来前端加上了credentials以后,从微信开发工具里看到,cookie确实发送了,而且包含了laravel_session的cookie,然而后端始终拿不到session数据。搜索了google,stackoverflow和laravel社区,大家有很多类似的情况,但都没结果,有的说在ajax并发请求频繁的时候会出现这种情况,有的说Session Facade不是在所有的地方都可以用,有的说是Laravel 4.x的一个bug,试遍了各种办法,也没解决,最后还是在入口处打了两种请求的callstack,果然发现了不同,正常的请求比api请求多经过了几个middleware的处理,在Http/Kernel.php里为两种请求配置了不同的middleware。

protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            'throttle:60,1',
            'bindings',
        ],
    ];

重点就是web经过的EncryptCookies middleware去解密cookie内容,而默认的api是不经过这个步骤的,因为laravel默认api请求是无状态的,需要每一次请求在header携带必要的token完成authentication,知道了原因,改起来很简单,为api请求也加上EncryptCookies的middleware。

protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            \App\Http\Middleware\EncryptCookies::class,
            'throttle:60,1',
            'bindings',
        ],
    ];

面试杂感

今天又有新人来公司面试,我看这个年轻人背景本科是西安交大,硕士是北航软件学院的,私下以为是个很不错的小伙子,稍加培养以后可以成为组里的生力军的。没想到,面下来,真的失望,我不敢相信交大和北航出来的学生是这种水平,就详细地问了问,他自己不好意思地说,虽然简历上写的是交大和北航,但交大是西安交大城市学院,一个三本学院,北航是软件学院在职读的。目前他已经取得了硕士学位,我更诧异现在软件学院办的这么滥了,教学水平是如何控制的,我让他找出无重复无序数组里面的一个值,找到了就返回序号,没找到返回-1,像下面这样,

int search(int[] data, int target);

我在白板上把原型写出来,他居然写不出一行代码,连for循环都写不出来,这就绝不是紧张可以解释的了。难道经过接近6年的科班训练,连这种程序都写不出来的话,究竟是怎么一回事?如果说跟个人有很大关系,但是毕竟北航授予了硕士学位,只能说北航的软件学院的教学质量堪忧,在砸北航的招牌,说实话,还不如有些培训结构出来的小伙儿。