程序员的自我修养

来源:互联网 发布:adaboost算法 编辑:程序博客网 时间:2024/06/18 15:16

提起工程师,我们会想起什么?桥梁工程师,负责设计稳固的桥梁,让我们安心通行;建筑工程师,负责设计雄伟的建筑,让我们有可靠的栖身之所;亦或是新中国总设计师邓小平,负责操盘国家的经济建设工程,让我们过上了丰衣足食的小康生活。

依我之见,我把能解决现实生活中存在的问题的人称之为工程师。桥梁工程师的课题是如何在河流的两边搭起一座桥梁,解决人们的通行问题;建筑工程师的课题是如何在平地上建起万丈高楼,解决人们的工作和生活住所问题;而小平同志的课题更是艰难,就是如何在新中国一穷二白的情况下,带领人民走向丰衣足食的生活。

同理,计算机工程师也是要解决现实生活中的问题。例如,如何为银行开发ATM交易系统;如何开发APP、网站;如何开发大数据分布式处理系统;如何开发深度学习框架等等。

评价一名计算机工程师的能力就是评价他解决问题的能力,而解决问题的能力又可以细分为以下几种能力。

一、抽象问题能力

现实生活中的问题通常没有那么直白,往往核心问题嵌套于表面问题,再加上一堆无关背景的干扰,我们往往陷于对无关细节的深入挖掘,对表面问题的冥思苦想,对核心问题的熟视无睹,缺少一种抽象思考的能力,无法挖掘出同类问题背后的解决机制。

例如以下一个小问题:

Given an integer array, find a subarray with sum closest to zero. //给定一个数组,找出一个子数组,使其和最接近于0Return the indexes of the first number and last number.            //返回找到的子数组第一个和最后一个数的下标索引ExampleGiven [-3, 1, 1, -3, 5], return [0, 2], [1, 3], [1, 1], [2, 2] or [0, 4]    //第0个数到第2个数,即 -3 + 1 + 1 = -1 和最接近0(在原数组中没有子数组和为0)ChallengeO(nlogn) time   //时间复杂度为O(nlogn)

一开始我们自然就想到暴力枚举所有子数组,但是存在O(n^2)个子数组,所以超时了,此法不通。然后我们看题目要求O(nlogn),嗯,看到logn我们是不是很容易就想起二分查找,然后看到nlogn,我们又联想到排序算法的时间复杂度为nlogn,map容器插入查找时间复杂度也为nlogn,那么我们就尽量往这方面靠。

我们的目标是求和接近于0的子数组,而子数组求和除了直接相加还有什么方法?对了,我们还可以通过相减来得到子数组的和。令f(i)=∑​0​i​​nums[i] 表示从数组下标 0 开始至下标 i 的和,那么f(b)-f(a)即为从下标为a到下标为b的子数组之和。那么求子数组和接近于0的问题便可转换为求f(i)(其中i = 0,1,…,n-1)这n个数中相差最小的两个数。

我们可先对这n个数进行排序,遍历一遍这n个数,求相邻元素的差值,这样就能得到这n个数中差值最小的两个数。而且排序算法的时间复杂度为nlogn,刚好符合问题的限制要求,这样我们就把原问题转换为排序问题,成功解决了这个问题。

二、逻辑能力

继续刚才的问题,我们已经确定了核心问题为排序问题。但问题的总体逻辑流程我们还没确定。即我们需先解决哪些子问题,再解决哪些子问题,子问题之间的数据如何传递,最后才能得到我们所需的结果。

我们脑中的逻辑流程是这样的:

  1. 根据给定的数组,求出n个子数组和。
  2. 由于最终结果是求子数组的下标,所以子数组和应与下标应形成对应关系。
  3. 对子数组和进行排序
  4. 对排序完的相邻元素遍历一遍求最小差值,记录最小差值出现的位置
  5. 根据最小差值出现位置,可得到对应的子数组下标,问题解决。

三、编程能力

编程能力是基础,若无法将算法实现,那么一切都是空中阁楼。我们要用适当的数据结构,语句,函数来将我们是想法表达出来。写出bug—free的代码是计算机工程师的基本素养。而这要求我们对编程语言十分了解(熟悉常见的数据结构),对现有的库十分熟悉(决定调用哪些库,要不要自己造轮子),对高级语言特性也要有所了解。

#include<iostream>#include<vector>#include<algorithm>#include<pair>using namespacestd;class Solution{public:       vector subclose(vector nums){              vector result;              int nums_size = nums.size();              vector<pari<int ,int>>subsum(nums_size+1);              for(int i=0; i<nums_size; i++){                     subsum[i+1].first =subsum[i].first + nums[i];                     subsum[i+1].second = i + 1;              }              stable_sort(subsum.begin(),subsum.end());              int min_diff = INT_MAX;              int close_sub_index = 0;              for(int i=1; i<nums_size+1;i++){                     int sum_diff =subsum[i].first - subsum[i-1].first;                     if(sum_diff < min_diff){                            min_diff = sum_diff;                            close_sub_index = i;                     }              }              result.push_back(subsum[close_sub_index-1].second);              result.push_back(subsum[close_sub_index].second-1);              return result;       }}

四、了解计算机相关知识

很多时候我们写出一个能运行的程序,并不意味着已经完全解决问题了。我们还要关注以下问题:顶层设计和底层实现。

顶层设计:
顶层设计通常与设计模式相联系,是编码前就应确定的系统框架,他能使整个工程易于维护,易于重构,易于理解与交接。
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。

底层实现:
首先我们看以下两段代码

for(int i=0; i<n; i++){    for(int j=0; j<n; j++)        res += a[i][j];}
for(int j=0; i<n; i++){    for(int i=0; j<n; j++)        res += a[i][j];}

初看我们觉得两个程序的运行效果是一样的,其实只要我们了解cache的概念,我们很容易就知道代码段1运行时间比代码段2短。现代计算机采用冯诺依曼体系,也就是CPU与内存分离,我们可以简单理解为CPU复杂计算,内存复杂存储数据。
CPU缓存(cache)是位于CPU与内存之间的临时存储器,它的容量比内存小的多但是交换速度却比内存要快得多。缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为CPU运算速度要比内存读写速度快很多,这样会使CPU花费很长时间等待数据到来或把数据写入内存。在缓存中的数据是内存中的一小部分,但这一小部分是短时间内CPU即将访问的,当CPU调用大量数据时,就可避开内存直接从缓存中调用,从而加快读取速度。

现代系统一般使用多线程提高系统效率,所以我们还要了解进程与线程的概念,系统如何调度线程与进程,如何保证操作是线程安全性的。

另一方面,系统安全也是我们要重点关注的方面。所以计算机网络我们也要了解,我们要知道计算机网络是如何分层的,当我们打开一个网站时,请求是如何发出的,数据又是如何返回的,不同主机之间是如何连接到一起的。

此外,若我们还了解过编译原理,那我们就知道a = a / 2 跟 a = a >> 1 执行效果一样,但效率低。

总而言之,深入理解计算机底层知识,能帮助我们写出更加高效,安全,健壮的代码。

总结

回到最初的问题:如何成为一名合格的计算机工程师?我认为分三步走,第一步先系统学习相关知识,第二步动手实践,第三步总结思考。要注意的是,我不是说要学完所有知识后,再动手实践,而是在你学到有关的知识后,马上动手实践,然后思考总结成果与收获。

1.系统学习知识:
因为我是软件工程专业,以下所列大部分是我之前所用的教材

编程语言:《c++ primer》,《effective c++》
数据结构与算法:《数据结构与算法分析》,《算法导论》
数据库原理:《数据库系统概念》
操作系统:《现代操作系统》
计算机组成原理:《计算机组成原理》
计算机网络:《计算机网络:自顶向下方法》
编译原理:《编译原理》
设计模式:《UML和模式应用》,《软件体系架构》

2.实践:

现在github上有很多优秀的开源代码,我们可以下载到本地,然后研读源码,先了解整体框架,逻辑流程,然后再到具体模块看具体实现,最后你可以试着模仿它实现一个简易版本的项目。这将对你理解理论知识有很大帮助。
还要注意在实际编码过程中保存良好的编码习惯,注意代码的可读性,简洁性,遵守常见的编程规则。

3.总结思考
现实生活中的问题千千万,我们不可能把所有坑都踩一遍。与此相反,我们倒是可能多次掉到同一个坑里面,所有我们需要思考与总结。
这样当我们解决一个问题时,其实我们已经解决了一类问题,而解决问题的能力恰恰是评价一个工程师水平的重要指标。
所以如何成为一名优秀的工程师,那就是不断遇见新的一类问题,并解决这些问题!

原创粉丝点击