技术攻关:从零到精通

任何一位工程师都不可能了解全部领域的技术知识;任何一个团队也不可能包含全部类型的专业人才。而一个完整的产品被开发出来,或者一个系统被构建出来,这个过程都会用到种类繁多的技术,通常来讲总会有一部分超出当前团队所能掌握的现有经验。这个矛盾怎么解决呢?这就须要工程师来进行技术攻关了。程序员

没错,工程师的真正价值就是把未知变成已知的能力。面试

如今假设你的leader交给你一件你历来没接触过的任务,好比,它可能涉及到研究若干框架以及系统架构,优化某些算法,设计和实现某一类型的网络协议,对音视频进行处理,研究系统底层,甚至这个过程可能会涉及到一些复杂的数学知识。总之,这项任务对你来讲有点复杂,你历来没有接触过,因此彻底没有概念。算法

你之因此领到这样一项任务,首先,是由于项目在当前或者将来须要解决这样的技术问题,而团队中没有人有现成的经验可以应付。另外一方面,你确定是在之前的工做中表现出了过人的学习和研究能力,你的leader才敢把这个工做交给你。编程

我在上篇文章《马拉松式学习与技术人员的成长性》中提到过,按技术领域来划分,编程能够分为「通常性」和「专业性」两大类。做为一件须要进行技术攻关的任务来讲,它有可能会涉及到一些「专业性」的领域了。我相信,每一位执着于技术的工程师,在他成长的过程当中,总会碰到相似的经历,去挑战一些本身未知的东西。在这个过程当中,既为团队解决了眼前的问题,也为本身打开了一片新的技术天地。这也是技术小白进阶到专业人才的必经之路。c#

今天,我就根据本身的经历和体验,说一说从零开始进行技术攻关的一系列过程,以及可能碰到怎样的一些问题。但愿你看完能有共同的感觉。设计模式

研究问题自己

有些状况下,你的leader无法告诉你具体应该作什么,他只是告诉你问题是怎样的,好比,视频播放老是卡顿,或者,用户老是说丢消息,再好比,用户反馈说搜东西的时候结果给的不许,打语音或视频电话老是接不通,老是有些图片访问不到,诸如此类。安全

咱们第一步应该作的,就是先研究问题自己是怎么产生的。首先试图从用户的角度去理解问题,而后从技术的角度去了解现有的实现,包括细节。这时候,全局的视角变得很是重要,你若是既能理解服务器的逻辑,也能理解客户端的逻辑,那么解决问题的思路会大大开阔。有些“系统性”的问题,属于设计缺陷,并非作局部改动就能解决的。这种问题,对于只接触过客户端编程或只接触过服务器开发的工程师,解决起来就有必定的难度,由于他们考虑问题的思路容易被见到的东西限制住。因此,适时扩大本身的知识域,永远都有好处。服务器

若是问题足够复杂,咱们可能还须要增长跟踪日志,便于在出现特殊的问题时可以分析它发生的过程;并定义性能指标,对现有系统的整体情况有一个量化的度量。它们一方面有助于咱们更深刻地理解当前系统,另外一方面也为后面的优化和重构过程提供了方向。微信

经过对问题自己的研究,咱们知道了系统的瓶颈或问题症结在哪里。若是咱们发现只有推翻现有系统彻底重构才能根治问题,那么接下来就跟从头开始设计一个全新系统的过程同样了。网络

对专业领域的Overview

在接触一个新领域以前,对它进行一个整体的概览是颇有必要的,这让咱们对后面整个的精力投入作到心中有数。

这个阶段的目标是,花最短的时间快速了解相关的各个概念,不求深刻理解,只求了解技术概况。因此,这个过程怎么快就怎么来,选择本身熟悉的方式。能够去你本身喜欢的技术社区搜索相关的文章,或者经过百度搜索一些概念。不少人不建议用百度来搜索技术问题,但了解一些概况仍是没有问题的,不过要适可而止。等到真正须要系统地研究技术细节的时候,仍是应该直接阅读更规范的资料(后面咱们还会提到)。

Run起来,得到感性认识

在当今的技术条件下,几乎什么技术领域的问题都有开源的软件能够借鉴。若是针对咱们要解决的问题能找到开源项目,那么很是幸运,咱们探索的过程会大大缩短。

不少技术领域都存在众多抽象的概念,经过前面Overview的阶段,咱们通常只能了解到这一层。而开源项目能帮助咱们快速地将抽象的概念具体化,得到感性的认识。下载一份代码,编译经过,而后运行起一个简单的Demo,从API层面去理解它(内部的实现尚不是重点)。

不少状况下,开源的实现不止一个,这就面临一个选择的问题。人们对于开源项目的第一印象通常来源于项目的入门教程(tutorial),可见一份好的文档对于一个开源项目来讲多么重要。根据我我的的经验,文档是否健全,也是选择开源项目的重要依据。

固然,在这个阶段,咱们要解决的主要还不是选择开源项目的问题,而是要经过快速Run起一个相关的实例来达到对技术得到感性认识的目的。注意这种感性认识是技术层面的,是至少基于API层面的。咱们常常看到,对于一些热门的技术领域,不少非技术人员也能略知一二,甚至对一些技术概念有所了解,可是,技术人员与非技术人员的区别,应该说,从这个阶段开始就有所不一样了。

同行交流

可以Run起一个实现,并从API层面粗略地了解一项技术以后,在这个认识的基础上,咱们就差很少能够找同行交流一下了。若是在咱们正要涉足去研究的领域里,咱们刚好认识一些这方面的专家,那么无疑是很是幸运的。逻辑清晰的技术高手,通常用不了几句话就能把某项技术的关键问题描述清楚了。从这种交流中,咱们受益不浅。

但要注意,咱们必定要在对该项技术有所了解以后,再去找专业人士交流。不然这种交流创建在信息严重不对称的基础上,就是极其低效的。对该项技术的初步了解,也是让咱们能问出真正有效的问题的基础条件。

研究Spec

我曾经写过一篇关于如何学习新技术的文章《技术的正宗与野路子》,在文中提到的一个重要的观点,就是必定要找到能称得上Spec的文档去阅读。所谓Spec,是集中体现该项技术的设计思想的东西,是高度抽象的描述,通常也是一份完备的、系统性的描述。它的存在形式有不少种,多是一份官方文档,也多是一份公开的技术标准,好比RFC或者W3C的规范,还多是以论文的形式,甚至与其它技术资料混杂在一块儿。

总之,你应该设法识别出哪些文档是Spec,而后在须要的时候通读它们。有些涉及到抽象概念的技术,你不读通这么一份Spec,有可能后面是看不懂代码的。这确实是比较费力的一个过程,但也正是这个过程,才真正开启了从门外汉向技术专家迈进的征程。

研究和选择具体实现

假设咱们找到了开源代码可供参考。前面咱们已经能Run起来一些小的Demo了,而且基本通读了一份大而全的Spec,如今须要研究的就是再深刻一层,看看这份Spec中的关键点是如何实现出来的。你前面已经花了不少时间来调研,这中间确定产生了不少疑问,好比有些抽象的概念以及类似概念之间的联系仍是难以理解,有些过程的实现初看起来并非那么地显而易见,而如今就到了该解决它们的时候了。头脑中的疑问和关键点,要本身总结出来,而后在代码中去找到答案,这是把抽象概念最终落地的一个过程。

若是有多个开源的实现,那么就涉及到如何选择的问题。有不少因素须要考虑:

  • 文档是否健全。
  • 提供的特性可否知足要求。
  • API层面逻辑是否清晰。不少代码在你初步接触了API这一层以后就大概知道本身是否是喜欢它了。
  • 模块化和抽象层次是否足够好。这决定了你把这份开源代码集成到本身项目中的难易程度。
  • 是否仍然有人维护。你固然但愿在提issue和pull request的时候有人可以响应。

研究类似产品的实现

有可能咱们要实现的东西其余家的线上产品已经提供相似的功能了。咱们有必要在实现本身的方案以前研究一下他们的作法(逆向工程),对比以后从而作出一个更优的实现。

具体怎么研究呢?两种常见的方式:一种是反解客户端的包,看看里面引用了什么,是否是在咱们调研过的那些技术范围以内;另外一种固然就是抓包,从网络通信上猜想他们用了哪一类技术。

网上浏览最佳实践

通过前面的调研,咱们基本上已经在头脑中产生了本身的方案了。但在真正实现它以前 ,咱们通常还想作一件事,就是「循证」。

记得胡峰同窗在他的微信公众号「瞬息之间」上,发过一篇文章《技术干货的选择性问题》,里面就提到了经过阅读技术文章来「循证」的作法。不少我的博主和团队博客会在网上发表他们本身系统的实现过程,以及系统先后版本的演进过程。若是咱们刚好找到相关的相似这样的文章,那么它们就有很大的参考价值。咱们从别人分享的技术方案中得到一个印证,确保本身的想法没有走向极端,或者漏掉了什么重要的东西。

结合本身的系统设计方案

对于复杂的系统,即便有开源的代码,一般也不能直接拿来就用。现有系统总有一些特殊性。这涉及到多种选择的可能性:

  1. 找到了一份代码扩展性很好的开源实现。这份代码有清晰的模块化和分层结构,咱们不须要改动原来的代码,只须要补充本身的一部分实现,再加上一些胶水代码(glue code),或者在原开源代码的基础上进行封装,就能把整个系统实现出来。这是最好的状况,工做量大大减小。
  2. 必须改动原来的开源代码,从新编译,才能实现本身的需求。这种状况通常来讲比较糟糕,主要是往后的维护可能会成问题。一方面,咱们产生了一份与原开源项目差异很大的代码分支,并且无法合并回开源项目;另外一方面,开源代码一般要考虑更通用的一些应用场景,它涉及到的问题域可能远远大于咱们要解决的。简单来讲就是,开源代码中有大量的与咱们的实际需求无关的代码,若是咱们要改动这份代码,咱们所须要掌握的信息要远远大于本身系统实际的要求。特别是在之后团队人员变更的时候,这份代码极可能变得没有人敢动。再就是,当原开源代码升级的时候,咱们很难跟着升级。因此,若是是决定做出这种对开源代码进行私有方式的改动的话,请慎重,并留下足够的文档说明。
  3. 开源代码与咱们的需求相差太远,或者找不到开源的实现,那么只能彻底本身实现了。还有一个迫使咱们从新实现的现实缘由,多是项目体积。若是咱们想在客户端引用的话,一个过重的实现就是不太合适的。咱们但愿引入的东西尽可能简单,体积小。

总之,这里的选择过程是比较痛苦的,由于它对后面的实现工做以及往后的维护影响很大。具体如何选择,除了要考虑开源实现与当前系统的实际需求之间的匹配程度,还要考虑预期收益和项目预算(budget),你有多少时间去完成整个的事情。

系统实现的过程

如今进入很关键的实现阶段了。实际上,对于一个复杂的系统,在真正写代码以前,须要首先进行设计,系统架构的设计和软件接口的设计。这个过程很是重要,花费的时间和精力极可能超过代码编写的过程。这个过程逼迫你在真正实现以前就必须想清楚系统在各个层面上是如何运行的,确保不会实现到一半推翻重作。

首先,系统架构的设计,划分出组成系统的各个组件(各个独立的进程,经过网络进行交互)。有两个问题须要在设计时就重点考虑:一个是可扩展性;一个是容错性。可扩展性说的是,当流量逐渐变大的时候,你的系统如何扩展。系统中有些组件是无状态的,有些是有状态的。无状态的组件通常经过增长节点,应用简单的负载均衡策略就能够扩展;而有状态的组件则须要明确扩展的方式。容错性说的是,系统应该主动处理失败状况,在设计中就应该考虑进去。你应该认可网络会丢消息,程序会崩溃,而后在这个基础上进行系统架构设计。好比,你想作到不丢消息,那么必须把网络丢包和处理异常的状况当作正常状况来处理,设计重传机制;而后既然有了重传,就不得不考虑去重机制。容错性还包括,系统应该具备从错误状态中恢复的倾向。固然,系统架构的设计还有不少因素须要考虑,好比高可用、高性能、可维护,等等,咱们这里就不展开说了。

其次,在更细的层面,要完成接口设计,也就是俗称的「面向接口编程」。这个过程的重要性怎么强调都不为过。咱们都知道「面向接口编程」,在面试的时候也常常讨论设计模式,但实际中真正按这种方式工做的人少之又少。形成这种情况的缘由多是,咱们日常作业务开发,在大部分状况下,都不用本身设计接口。好比作客户端开发,各类MVC, MVP模式已经把代码框架都定义好了,咱们只用往接口实现里填东西。

但咱们应该知道,当为一个新系统编写代码的时候,代码应该从接口设计开始。先用代码定义出各层的接口(包括回调接口),没有实现,只是可以编译经过。有了这些接口,就能够拿它们与同事进行很是细节的讨论了。应该先把接口讨论得足够清楚,再进行下一步的具体实现。这也是一个比较痛苦的过程,咱们须要反复抉择,而一般「选择」就意味着痛苦。根据我我的的经验,设计接口代码的过程,通常都要先后改不少遍,才能达到令本身基本满意的程度。

接口设计的时候,要时刻考虑这两个问题:

  • 功能层次。也就是系统包括哪些接口,哪些功能放到哪些接口里面,不一样的接口之间的关系如何。咱们可能还须要画出相似UML(Unified Modeling Language)那样的类图。
  • 实例运行模型。系统运行起来以后,接口的各个实例的生命周期,以及各个实例之间的交互关系和数量关系,是一对一,仍是一对多。虽然在编写接口代码的时候,还不要求写出实现,可是一个不错的实践方式是,写完接口,先生成一个空的实现类,而后把能代表实例引用关系的代码先写出来,再进一步把各个接口的实例建立代码写出来(解决了实现参数的注入问题),这样一个系统的「骨架」就出来了。

在编写和修改接口代码的过程当中,还有几个问题值得考虑:

  • 是否引入响应式编程(Reactive Programming)的思想。实践证实,采用响应式编程和传统的回调方式(callback)设计出来的接口形式,存在很大的不一样。
  • 给接口起名字很是重要(包括各个类名、方法名、参数名等)。起名字实际上是个大问题,就像给本身的小孩起名字同样难!名字起的好很差,直接反映了经过系统抽象划分出来的各个角色是否是合理。
  • 能称得上「接口」的代码,从一开始编写就要有很是详细的代码注释。
  • 考虑线程模型。被上层调用的代码预期在哪些线程上执行,回调的代码又在哪些线程上执行。是多线程的环境,仍是单线程的环境,这直接影响后面的实现应该怎么来作。通常来讲,多线程的环境是存在一个线程池,这个线程池是外部调用者提供,仍是接口内的实现来提供,这个要规定清楚。另外就是考虑可否把多线程问题规避,变成单线程的编程问题。好比,在有些异步编程的状况下,充分利用Java的Executor,或者Android开发中的Looper,或者RxJava之类的框架,就可能达到相似的目的。

最后,就是愉快地实现了。只要前面的过程作得比较细致,编写实现代码基本就是水到渠成了。在实现中有一个很是重要但容易被忽视的问题是——日志(log)。每一个人都知道怎么打日志,但打一份好的日志,实际没有几我的可以作到的。通常来讲,若是没有足够的重视,工程师打出来的日志,或者过于随意,或者逻辑缺失。一份好的日志其实要花不少精力来调整细节,把程序运行当作一个状态机,每个关键的状态变化,都要在日志中记录。一份好的日志其实反映了一套好的程序逻辑。总之,打日志的目标是:若是线上发生奇怪的状况,拿过这份日志来就能分析出问题所在。这在客户端上分析线上问题的时候尤为有用。


前面讲到的各个过程并非要严格按照这样的步骤进行,实际中有些过程要先后交叉,甚至反复进行。中间碰到问题,可能还要退回到前面的步骤,从新进行。

在时间、精力和项目进度各类条件容许的状况下,尽可能把事情作到当前认为「最好」的状态。固然,若是因为客观条件,一时没有那么多时间去作到完美,也不要太过沮丧,后面可以持续优化才是最重要的。

进行一项技术攻关,从这个过程当中学习新的东西,这是一个利用现有各类资源来学习和解决问题的过程。每一步并不是必不可少。你参考的东西越多,作出一个差劲的实现的可能性就越小,但付出的精力也就越多。

关键的关键,当团队中出现现有经验没法解决的问题的时候,你可以站出来,敢于承担。这样,你的技术之路也会愈来愈宽广。

(完)

其它精选文章

相关文章
相关标签/搜索