软件开发的那些事
2011-03-03 10:19:59 阿炯

想成为一名优秀的软件开发人员需要很长时间的培训和实践。但是如果不遵循合适的原则,即便是再好的程序员也会成为失败的牺牲品。不经意间你就会养成一些可怕的坏习惯,它们可能会一而再再而三地出现,甚至对于经验最为丰富的程序员而言也是如此。我认为软件开发至少存在七宗罪。那么,就请看看欲望、暴食、贪婪、懒惰、愤怒、嫉妒和骄傲会为你的最新编程项目带来哪些意想不到的损失吧。

程序员七宗罪



软件开发七宗罪,你有几条?

软件开发第一宗罪:欲望(过度设计)

现代编程语言趋向于不断增加更新的功能让其臻于成熟。程序员们一层一层地往上堆叠抽象的代码,用新设计的关键字和结构来增加代码的可读性和可重用性——只要你肯花时间去学习如何正确地使用它们。
与此同时,编程的原则在这些年里有所改变。今天,你可以饱览到成千上万的设计方案和模式,而且每几个月就会有人想出新的开发方案。此外,开发人员总是信誓旦旦地宣称这些开发方案会让你成为程序员中们眼中的佼佼者。

但是纸上谈兵往往不见得在实际操作中奏效,这个道理很简单,打个比方来说就是你可以去做某些事情但是并不意味着你应该去这么做。就像编程大师Joel Spolsky所说的,那些盲目迷恋自己工具的程序员们都不可避免地忽略了这一点,甚至于最简单的项目也可能葬送进开发的地狱。所以,必须抵制这种不可取的冲动,那么首先要做到的就是坚持你最初的设计方案。

软件开发第二宗罪:暴食(不进行重构)

没有比开发软件更令人可喜的事情了。一旦你有一个正在开发的产品,就会很容易受到开始筹划下一次迭代的引诱。新产品应该具备什么样的新特征呢?我们在第一轮实施的时候没有注意到哪些问题?

人们总是很容易忘记代码很少能在运行的时候保持最佳状态。然后,当新功能不断地累积直至几个轮回的发展之后,程序员们往往会倾向于复合过去发生的错误,这样就导致了一个臃肿、脆弱的代码基础,难以进行有效地维持。

所以,在添加新功能之前尽量克制自己,对现有代码的质量和可维护性进行评估。对于每一次新一轮的开发,都必须将代码重构列入预算范围之内。用户可能只会关心每一个版本的新功能,但是从长远来看,他们一定会更希望你保持产品的精炼性。

软件开发第三宗罪:贪婪(团队之间的竞争)

对于财富和权利的过度欲望——要不然如何解释程序员与自己同僚之间竞争的动机?当一个团队得到了其他团队泄露出来的电子邮件,就开始进行闭门会议和开发。接下来的事情你应该知道,这个团队编写了一个代码库,它已经超越了其他团队已经完成的编码库功能的一半以上。

开发团队很少会出于恶意来进行重复的工作,但是他们往往缺乏明确的目标和责任感。得到的结果则是多余、无力的代码库,更不要说预算的损失和之前付之一炬的努力。经营开发项目的首要任务之一应该是了解其他团队正在做什么,然后所有的团队都朝着一个共同的目标去努力。分享与共享应该成为开发人员的座右铭。

软件开发第四宗罪:懒惰(不验证输入)

基本编程容易犯的错误清单很长,但是没有验证输入这样的错误非常低级以至于不能不从别的角度来思考犯这类错误的原因。这个看似低级的错误仍旧出现在许多经验丰富的程序员编写的代码中,这一点十分令人费解。然而,很多普通的安全漏洞,从缓冲区泛滥成SQL注入攻击,却可以直接追溯到用户输入的代码没有进行正确格式验证这一点。

现代编程语言提供了许多工具来帮助程序员来避免类似情况的发生,但是他们必须对其使用得当。切记,一个JavaScript的Web表单验证输入可能很容易被在浏览器中禁用的JavaScript回避,或者干脆不使用浏览器进行访问。输入验证应该是你应用程序的核心部分,而不是在用户界面上煞费苦心。如果做不到这一点只能归咎于程序员的懒惰了。

程序开发第五宗罪:愤怒(不对你的代码进行注释)

对于你的同僚而言,有什么行为能比不对你的代码做出任何注释这种行为更具敌意?我自己写的我当然知道:精心编写的代码是它本身最好的文档资料。那么,你知道吗,其他人能看懂吗?其实这些你自认为值得骄傲的代码可能并非天衣无缝。

程序员们自己很可能很快就会遗忘他们现在所写的代码,但是这些代码将在他们离开以后继续存在很长一段时间。对于取代他们的程序员而言,要想搞清楚每一个代码真正代表什么是一件吃力不讨好的工作。鉴于此,在你编写程序的时候,行行好吧,多给他们留下一些暗示。

但是请记住,难以理解的注释或者注释过多都与不做任何注释的性质同样恶劣。类似于“这已经损坏”或者“不要碰这个”的注释对于任何人都没有什么帮助。也不要做多余的评论来解释简单的操作,比如变量初始化。代码就是其本身最好的文件材料,所以出现的注释评论应该尽可能解释原因和本质。

软件开发第六宗罪:嫉妒(不使用版本控制)


也许你有理由对于你的项目不推行版本控制。也许一开始你的项目很小。但是今天,功能强大并且高效的版本控制系统已经可以免费进行使用。服务供应商甚至可以对分布式项目提供价格低廉的托管代码服务。所以没有理由不在一开始就使用一个代码库,即便是再小的项目也是如此——除非,你无法容忍除了你以外的任何人实施代码变化。

软件开发第七宗罪:骄傲(不进行单元测试)

你往往会认为自己的编程工作相当出色,但是你又如何知道自己做的到底是否唯美无缺呢?有什么指标来对你的工作好坏进行衡量吗?

除非你已经在特定的测试环境下对你的代码进行了验证和测试,否则你就不能证明它能像广告中说的那样完美无瑕。但是太多的开发人员并不对他们的代码进行单元测试。他们声称花时间进行测试就没有时间推行新功能了。事实上,一些开发人员甚至不将质量测试写入他们项目的预算范围。

那么我又能说什么呢?只能期待他们在品尝失败的滋味之前将这股傲气抛之脑后。一旦存在缺陷的代码抵达客户手中的时候,再撤销它们就未免太迟了。在进行代码传输之前,进行越多的单元测试,就能在今后越多地避免不必要的损失。

原文标题:The 7 deadly sins of software development

关于七宗罪

贪婪,失控的欲望,是七宗罪中的重点。其他的罪恶只是无理欲望的补充。

色欲:肉体的欲望,过度贪求身体上的快乐。

饕餮:贪食的欲望,浪费食物或者过度放纵食欲,过分贪图逸乐皆为饕餮一罪。 

妒忌:财产的欲望,因对方拥有的资产比自己多而心怀怨恨(此处的资产并非限定于财产,更多的指才能、才华) 

懒惰:逃避的欲望,懒惰及浪费所造成的损失为懒惰一罪的产物。 

傲慢:卓越的欲望,过分自信导致的自我迷恋,以及过分渴求他人的关注为傲慢。 

暴怒:复仇的欲望,源于心底的暴躁,因憎恨产生的不适当邪恶念头。

七宗罪在拉丁语中为: “superbia”,“invidia”,“ira”,“accidia”,“avaritia”,“gula” and “luxuria”。

上文源自:51CTO


编程基础“四大件”

基础四大件包括:数据结构和算法、计算机网络、操作系统、设计模式。

这几个技术和具体语言无关,但是如果是做偏软件的工作,即使是嵌入式软件,都是非常重要的,可以大大拓宽自己的职业生涯和技术深度。

一、数据结构和算法

大厂开发相关的必备技能,最后的算法题部分肯定是要过去的。

基础的数据结构包括:

--数组(Array):
--一组具有相同数据类型的元素按连续内存空间存储。
--可以通过索引快速访问元素。

--链表(Linked List):
--一组元素,其中每个元素包含数据和一个指向下一个元素的指针。
--常见的类型有单向链表、双向链表和循环链表。

--栈(Stack):
--一种遵循后进先出(LIFO, Last In First Out)原则的数据结构。
--常用于递归和表达式求值。

--队列(Queue):
--一种遵循先进先出(FIFO, First In First Out)原则的数据结构。
--常用于任务调度和广度优先搜索(BFS)。

--哈希表(Hash Table):
--使用哈希函数将键映射到值的数据结构。
--提供快速的插入、删除和查找操作。

--树(Tree):
--一种由节点组成的分层数据结构,其中每个节点可以有零个或多个子节点。
--常见的类型有二叉树、二叉搜索树(BST)、平衡二叉树(如AVL树)、红黑树等。

--图(Graph):
--一组节点和连接这些节点的边的集合。
--可以表示对象之间的关系,常用于路径搜索、网络流问题等。

基础的算法包括:

--排序算法:
--冒泡排序(Bubble Sort):通过重复遍历要排序的数列,比较每对相邻元素,如果它们的顺序错误就把它们交换过来。
--选择排序(Selection Sort):每次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
--插入排序(Insertion Sort):通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
--归并排序(Merge Sort):采用分治法,将问题分成一些小的问题然后递归解决,最后再将各个已排序的小段合并起来。
--快速排序(Quick Sort):通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,然后再按此方法对这两部分分别进行快速排序。

--搜索算法:
--线性搜索(Linear Search):从数组的第一个元素开始,依次将当前元素与目标值进行比较,直到找到目标值或搜索完整个数组。
--二分搜索(Binary Search):在有序数组中查找某一特定元素的搜索算法,搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。

--动态规划算法(Dynamic Programming):
--用于解决最优化问题,通过将问题分解为更小的子问题,并存储子问题的解决方案以避免重复计算。

--贪心算法(Greedy Algorithm):
--在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。

--回溯算法(Backtracking):
--一种通过探索所有可能的候选解来找出所有解的算法,如果候选解被确认不是一个解(或者至少不是最后一个解),回溯算法会通过在上一步进行一些变化来丢弃该解,即“回溯”并尝试另一个可能的候选解。

--分治算法(Divide and Conquer):
--将一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。


二、计算机网络

这里说的计网主要是指TCP/IP协议栈,这是当今互联网和通信的基础,很多面试都会涉及这部分考察,另外这部分也是做很多通信开发的基础。

TCP/IP主要协议包括:协议模型由四个层次组成,分别是应用层、传输层、网络层和网络接口层。以下是TCP/IP协议族中的主要协议及其作用:

1、应用层协议

应用层是TCP/IP协议集的最高层,负责处理特定的网络应用程序,如电子邮件、文件传输和网页浏览。应用层协议包括HTTP、FTP、SMTP、DNS等。这些协议定义了应用程序如何使用网络资源进行通信。

--HTTP(HyperText Transfer Protocol):用于在万维网(WWW)上传输超文本的协议。它是无状态的、面向对象的协议,基于请求/响应模型工作。客户端(通常是浏览器)向服务器发送HTTP请求,服务器处理请求并返回HTTP响应。

--HTTPS(HyperText Transfer Protocol Secure):HTTP的安全版本,通过TLS/SSL协议加密数据传输,确保数据的机密性和完整性。

--FTP(File Transfer Protocol):用于在网络上传输文件的协议。它支持文件的上传、下载和管理。

--SFTP(Secure File Transfer Protocol):通过SSH(Secure Shell)加密传输文件的协议,确保文件传输的安全性。

--SMTP(Simple Mail Transfer Protocol):用于发送电子邮件的协议。它定义了邮件如何从发件人传输到收件人的邮件服务器。

--IMAP(Internet Message Access Protocol):用于从邮件服务器读取电子邮件的协议。与POP3不同,IMAP支持在服务器上管理邮件。

--POP3(Post Office Protocol version 3):另一种从邮件服务器读取电子邮件的协议。与IMAP不同,POP3通常将邮件下载到本地设备并从服务器上删除。

--DNS(Domain Name System):用于将域名解析为IP地址的系统,是互联网的重要基础设施之一。

--DHCP(Dynamic Host Configuration Protocol):用于动态分配IP地址和其他网络配置的协议。

2、传输层协议

传输层负责提供端到端的通信服务。传输层协议包括传输控制协议(TCP)和用户数据报协议(UDP)。

--TCP(Transmission Control Protocol):提供可靠的、面向连接的服务。它确保数据完整、无损并且按顺序到达。TCP尽量连续不断地测试网络的负载并且控制发送数据的速度以避免网络过载。

--UDP(User Datagram Protocol):提供不可靠的、无连接的服务。UDP不对数据包是否已经到达目的地进行检查,并且不保证数据包按顺序到达。UDP协议传输效率高,但可靠性略低,适用于传输可靠性要求不高、体量小的数据。

3、网络层协议

网络层负责数据包的路由和转发。网络层协议包括因特网协议(IP)、地址解析协议(ARP)、互联网控制报文协议(ICMP)等。

--IP(Internet Protocol):最重要的网络层协议,它定义了数据包的格式和地址结构,并负责数据包的路由。IP协议是一种无连接、不可靠的分组传送服务的协议。

--ARP(Address Resolution Protocol):用于将IP地址解析为物理地址(如MAC地址),以便在局域网中进行数据通信。

--ICMP(Internet Control Message Protocol):用于发送错误和状态信息,如网络不可达、主机不可达等。

4、网络接口层协议

网络接口层负责与物理网络的接口,包括以太网、Wi-Fi等。网络接口层协议定义了如何在物理网络上传输数据帧,以及如何处理链路层的错误和冲突。网络接口层协议包括以太网协议、PPP协议等。

综上所述,TCP/IP协议族中的各个协议共同协作,确保了数据在网络中的高效、可靠传输。


三、操作系统

操作系统更是软件开发的重中之重,尤其是对于嵌入式开发者来说,如果对操作系统掌握不够深入的话那务必得下功夫了。操作系统是此类开发重点(这里重点说下Linux系统):

1、Linux操作系统基础

--基本概念:理解Linux操作系统的内核、文件系统、进程管理、权限和用户管理等基本概念。
--命令行操作:熟悉Linux命令行接口(CLI),掌握常用的命令,如文件操作、进程管理、网络配置等。
--Shell编程:学习Shell脚本语言(如Bash、Zsh、Ksh等),掌握脚本语法和逻辑,能够编写脚本来自动化日常任务和系统管理操作。

2、Linux系统编程

--系统编程接口:理解并掌握Linux系统编程接口,包括标准库函数和系统调用。
--进程与线程:深入理解Linux多任务编程中的多进程和多线程,以及进程间通信(如pipe、FIFO、消息队列、共享内存、signal、信号量等)。
--同步与互斥:学习同步与互斥对共享资源访问控制的重要性,确保多个进程或线程能够安全地访问共享资源。

3、Linux网络编程

--TCP/IP协议:理解TCP/IP协议栈的工作原理,掌握socket编程接口。
--网络编程API:熟悉Linux网络编程相关API,如TCP/UDP网络编程、Web编程开发等。
--并发服务器:学习如何实现TCP协议服务器的编程方法和并发服务器的设计。

4、Linux内核开发

--内核原理:深入理解Linux内核的工作原理,包括进程管理、内存管理、文件系统、网络子系统等。
--内核模块编程:学习如何编写和加载内核模块,以便在内核空间运行自定义代码。
--设备驱动开发:掌握Linux设备驱动的原理和框架,熟悉字符设备、块设备、网络设备等驱动的开发。

5、Linux开发工具与环境

--编辑器与IDE:熟悉Linux下的文本编辑器(如Vim、Emacs)和集成开发环境(IDE,如Eclipse、Visual Studio Code)。
--编译器与调试器:掌握GCC编译器和GDB调试器的使用,以便进行高效的代码编写和调试。
--版本控制:熟悉Git等版本控制系统的使用,以便进行代码管理和团队协作。

6、Linux系统安全与优化

--系统安全机制:了解Linux系统的安全机制,如SELinux、AppArmor等,确保系统的安全性。
--性能优化:学习性能分析工具(如gprof、valgrind、perf等)的使用,掌握代码优化技巧,提高程序性能。
--稳定性与可靠性:通过充分的测试(如单元测试、集成测试、系统测试等)来确保软件的稳定性和可靠性。


四、设计模式

设计模式可以提高代码的可复用性、灵活性、可扩展性。

最经典的教材就是23种设计模式,这里也不用全部记住,掌握最常见的几种就可以,其他可以用到再说。

23种设计模式有哪些:

23种设计模式是由Gang of Four(GoF)在《设计模式:可复用面向对象软件的基础》一书中提出的经典设计模式,它们分为三大类:创建型模式、结构型模式和行为型模式。以下是这23种设计模式的详细分类及介绍:

1、创建型模式(5种)

--单例模式(Singleton):确保一个类只有一个实例,并提供一个全局访问点。
--工厂方法模式(Factory Method):定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
--抽象工厂模式(Abstract Factory):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
--建造者模式(Builder):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
--原型模式(Prototype):用原型实例指定创建对象的种类,并且通过拷贝这些原型来创建新的对象。

2、结构型模式(7种)

--适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口。适配器模式让原本由于接口不兼容而不能一起工作的那些类可以一起工作。
--桥接模式(Bridge):将抽象部分与它的实现部分分离,使它们都可以独立地变化。
--组合模式(Composite):将对象组合成树形结构以表示“部分-整体”的层次结构。它使得客户对单个对象和组合对象的使用具有一致性。
--装饰器模式(Decorator):动态地给一个对象添加一些额外的职责。就扩展功能而言,装饰器模式比生成子类方式更为灵活。
--外观模式(Facade):为子系统中的一组接口提供一个一致的界面,定义了一个高层接口,这个接口使得这一子系统更加容易使用。
--享元模式(Flyweight):运用共享技术有效地支持大量细粒度的对象。
--代理模式(Proxy):为其他对象提供一个代理或占位符,以控制对这个对象的访问。

3、行为型模式(11种)

--模板方法模式(Template Method):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
--策略模式(Strategy):定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。
--命令模式(Command):将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。
--责任链模式(Chain of Responsibility):为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。
--状态模式(State):允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。
--观察者模式(Observer):定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。
--中介者模式(Mediator):用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
--迭代器模式(Iterator):提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。
--访问者模式(Visitor):表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
--备忘录模式(Memento):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。
--解释器模式(Interpreter):给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。


程序员锻造高质量代码之道

在软件开发的广阔天地中,高质量的代码是项目的基石,它关乎软件的稳定性、可维护性、可扩展性以及用户体验。那么程序员如何才能写出高质量的代码呢?本文将从基础知识、编程习惯、持续学习、代码审查和测试等多个方面,为您提供一份全方位的指南。

1.夯实基础知识
高质量代码的起点是扎实的编程基础知识。程序员应熟练掌握编程语言的核心特性,包括数据类型、控制结构、函数、面向对象编程等。此外,对算法和数据结构有深入了解,能够帮助程序员写出更高效、更简洁的代码。

2.养成良好的编程习惯
编程习惯对于代码质量有着至关重要的影响。程序员应该遵循简洁明了、易于理解的命名规范;编写清晰的注释,解释代码的功能和逻辑;保持代码结构清晰,遵循适当的缩进和排版;避免使用魔法数字和硬编码,而是使用常量或变量来代替。

3.注重代码的可读性和可维护性
高质量的代码应该易于阅读和维护。程序员在编写代码时,应该考虑到代码的可读性和可维护性。这意味着代码应该具有良好的结构,逻辑清晰,避免过度复杂化和冗余。同时,合理使用设计模式、遵循SOLID原则等,也能提高代码的可维护性。

4.持续学习和自我提升
技术日新月异,程序员需要保持持续学习的态度,不断跟进新的编程语言和工具。通过参加技术交流会、阅读技术博客、参与开源项目等方式,程序员可以不断提升自己的技术水平,写出更高质量的代码。

5.重视代码审查和测试
代码审查和测试是确保代码质量的重要手段。通过代码审查,可以发现潜在的错误和问题,提高代码的可读性和可维护性。而测试则能够确保代码的功能正确、性能稳定。程序员应该编写单元测试、集成测试和系统测试,确保代码在各种场景下都能正常运行。

6.注重代码的性能和安全性
高质量的代码不仅要有良好的结构和可读性,还要关注性能和安全性。程序员应该熟悉常见的性能优化手段,如缓存策略、数据库优化等,以提高代码的执行效率。同时,要关注代码的安全性,避免潜在的安全漏洞,如SQL注入、跨站脚本攻击等。

7.实践敏捷开发和持续集成
敏捷开发和持续集成是提高代码质量的有效方法。敏捷开发注重快速迭代和团队协作,能够帮助团队更好地应对需求变更和风险。而持续集成则能够实现代码的自动化构建、测试和部署,确保代码的质量在整个开发过程中得到持续监控和改进。

写出高质量的代码并非一蹴而就的过程,它需要程序员具备扎实的基础知识、良好的编程习惯、持续学习的态度以及注重代码审查和测试等多方面的素质。通过不断地实践和总结,程序员可以逐步提高自己的代码质量,为项目的成功实施和持续发展奠定坚实的基础。需要对计算机的基础运行环境有充分的掌握。

操作系统

计算机体系结构


操作系统提供的一个重要功能是管理底层的硬件并且将其抽象为API向上层提供服务。

原始的程序是直接跑在物理内存上的,比如一共有100M内存,A程序需要10M,B程序需要50M,C程序也需要50M,那么如果现在A和B在跑,C就没法跑,因为内存不够用,此外还有很多问题:
地址空间不隔离:这对于程序的安全有很大的问题,A程序可能有意或者无意,导致改写了B程序的内容,这会导致B程序出现不可预料的问题;
内存使用效率低:由于整个程序都要加载到内存里,如果我们想要运行C,这个时候必须将B搬出来才可以,但是这又会导致B无法运行;
程序运行的地址不确定:由于每次程序进入内存,都要分配一块连续的足够大的空闲的内存,久而久之,此次分配的内存地址与上一次所在 的内存地址未必相同,所以程序每次运行的内存地址是不确定的。

因此引入了分段的管理方法,也就是不再让程序直接使用物理内存,转而使用虚拟内存地址,我们按程序所需要的大小,为其分配虚拟内存地址, 这就叫分段,这样解决了上面的1和3的问题,借助虚拟内存,可以使得程序每次都假使自己使用的内存地址从0开始,而硬件直接将他们映射到物理内存的某一段。为了解决问题2,引入了分页的技术。也就是把整个程序切成固定大小的长度,比如4KB。每一个4KB就是一页。虚拟内存空间的页叫做 虚拟页(Virtual Page, VP),物理内存的页叫做物理页(Physical Page, PP),磁盘中的页叫做磁盘页(Disk Page, DP)。通过MMU来进行内存位置的映射。

线程和进程

一个标准的线程有以下几个部分组成:
线程ID
当前指令指针(PC)
寄存器集合
堆和栈

线程是程序执行流的最小单元,也是CPU调度的最小单元。一般来说,一个进程由一个到多个线程组成,各个线程之间共享程序的内存空间;比如代码段,数据段,堆等,以及一些进程级别的资源比如所打开的文件和信号等。一般把频繁等待的线程称之为I/O密集型线程,很少等待的线程则叫做CPU密集型线程。

三种线程模型:
内核线程:用户态线程 = 1:1:缺点是内核线程数量有限,所以用户态线程真正并发的数量也有限;内核线程上下文切换开销大
内核线程:用户态线程 = 1:1:这个几乎不存在,因为内核线程一般都不止一个
内核线程:用户态线程 = 1:1:这个结合了上述两种的好处,可以有无数的用户态线程和较低的切换开销

静态链接和动态链接

一个简单的'Hello World'的C程序,其实有这么几个步骤:
预处理:把源码里的头文件、宏定义等展开,插入,生成为一个 .i 的文件
编译:把上一步得出来的文件进行一系列词法分析,语法分析,语义分析,优化之后生成对应的汇编代码
汇编:把汇编码变成机器码
链接:由于上述步骤生成出来的变量、地址等都还不是确定的,因此在这一步,进行这种工作。把各个模块之间的互相引用的部分都处理好, 使得各个模块之间能够正确的衔接。主要包括:地址和空间分配,符号决议和重定位等。

静态链接完成之后,整个程序运行所需要的库等,都会打包在可执行文件里,因此空间上有点浪费(可以参考Go编译出来的程序,都比较大);此外还有一个问题就是,没有办法动态的更新程序中的一部分,只要代码或者其依赖的库有变化,就必须重新打包和发布。因此有了动态链接,动态链接就是把程序的模块分割开来,形成独立的文件,不对那些组成程序的目标文件进行链接,而是把这个工作退后到了运行时。需要的时候再进行链接的工作。


软件开发的那些公司们

软件外包就是企业为了专注核心竞争力业务和降低软件项目成本,将软件项目中的全部或部分工作发包给提供外包服务的企业完成的软件需求活动;现在业务流程外包(BPO)已经成为外包服务新的发展趋势,在未来几年内将成为外包的主要内容。

软件开发公司排行榜

一线:华盛恒辉、五木恒润、北京华盛恒辉、北京五木恒润、中科软、博彦科技、浪潮、亚信科技、新致软件;

二线:华盛恒辉、五木恒润、北京华盛恒辉、北京五木恒润、法本、德科、东软集团、海隆软件、宇信科技、汉德、用友软件;

最近几年随着创业风气的发起,已经涌起创业项目外包公司的兴起,已经不仅仅局限为了降低成本,更多的是为了解决自己雇佣技术人员或者无法管理技术人员的难题。

1、什么是软件外包?软件外包就是企业为了专注核心竞争力业务和降低软件项目成本,将软件项目中的全部或部分工作发包给提供外包服务的企业完成的软件需求活动。

2、现在业务流程外包(BPO)已经成为外包服务新的发展趋势,在未来几年内将成为外包的主要内容。

3、BPO包括人力资源、采购、财会、客户中心、后勤、研发、营销、工厂运作、培训,这些大类还可以进一步细分。

4、不仅IT行业需要BPO,而且BPO的每项业务都离不开IT业务的支持,从而产生IT外包机会。

5、一个软件开发项目通常要经历需求分析、设计、编程、测试等几个大的阶段。

6、其中设计又包括整体设计、系统设计(把整体架构变成一块块系统)、详细设计几个环节。

7、详细设计之后软件就变成了一块块模块,这以后才进入编程。

8、到了编程阶段时,最后就剩下软件蓝领为模块的Coding工作,在印度通常由受过一两年训练的高职毕业生担任。

9、软件最后的测试又是一个复杂过程——有单元测试(小模块测试)、系统测试(块与块的联系整合)、总体功能测试。

10、期间由测试编程工程师编写测试工具,制定测试规则,其难度不亚于系统框架的制定。

11、最后才由测试工程师完成测试的任务。

12、外包软件测试有望成为小作坊软件业业务增长点企业若想把承接国际软件外包的业务做强做大,必须与国际软件市场接轨——包括英语的理解与沟通能力,技术接轨、管理接轨、做事方法接轨、知识产权接轨。

13、还有质量保障、信誉度保障、风险控制能力以及知识产权保障等方面。

14、以上条件显然国内大多数小作坊式软件企业还做不到。

15、但这也未必表示小作坊软件企业就没有机会承接软件外包服务。

16、因为软件外包项目中,软件测试项目最适合中国现阶段小作坊软件企业的行业特色。

17、软件测试是保证软件质量的最重要的手段使用低质量的软件,在运行过程中可能会产生这样那样的问题,可能为使用单位带来造成延误工作或者造成生命财产的损失。

18、而软件测试是为保证软件用户免于遭受损失的最重要的手段。

19、什么是软件测试?1983年IEEE定义为:使用人工或自动手段来运行或测定某个系统的过程,其目的在于检验它是否满足规定的需求或是弄清预期结果与实际结果之间的差别。

20、现代的软件开发工程是将整个软件开发过程明确的划分为几个阶段(参见下图),将复杂问题具体按阶段加以解决。

21、这样,在软件的整个开发过程中,可以对每一阶段提出若干明确的监控点,作为各阶段目标实现的检验标准,从而提高开发过程的可见度和保证开发过程的正确性。

22、经验证明,软件的质量不仅是体现在程序的正确性上,它和开始编码以前所做的系统需求分析,软件设计密切相关。

23、许多软件使用中出现的错误,未必是编程人员在编码阶段造成的,反而在程序设计,甚致在需求分析时就埋下了祸因。

24、这时,对软件工程的错误纠正,就必须追溯到软件开发的最初阶段。

25、如果是这样又增大了软件的开发费用。

26、为了保证软件的质量,专案管理就应该着眼于整个软件生存期,特别是在开发阶段的系统分析。

27、所以软件测试的概念和实施范围必须包括在整个开发各阶段的复查、评估和检测。

在当今竞争激烈的商业环境下,许多公司选择将IT服务外包给专业服务商,而不是自行招聘IT人员。这种趋势背后存在着一些明显的优势,在此介绍选择IT外包比自招聘IT人员有哪些优势。


1、降低成本
招聘、培训及管理内部的IT团队需要大量的人力、物力和财力投入。公司需要为IT人员提供工资、培训、福利和办公设施等费用。而通过IT外包,公司只需支付一定的服务费用,可以节省大量的成本。外包公司通常能够提供有竞争力的价格,由于规模经济和专业化程度较高,能够更有效地管理成本,从而使成都企业受益。而且,对于小微企业来说,自招专职IT工程师,成本要高很多,不仅浪费人才,还不能稳定人心,所以将IT运维外包出去,一年的服务费仅是自招一个月的费用,有问题随叫随到,在节省成本的同时,保障企业业务。

2、专业知识和技术
IT外包公司通常拥有丰富的经验和专业的技术知识,工程师都是经过专业培训和实践的,熟悉最新的技术趋势和最佳实践。通过与专业的IT外包公司合作,企业可以获得更高质量的服务和解决方案,从而提升业务的效率和竞争力。外包公司能够快速适应新技术,并及时进行更新和升级,为企业提供创新和前瞻性的解决方案。

3、灵活性和可扩展性
IT外包使企业能够根据业务需求灵活地调整人力资源。当公司业务规模扩大或缩小时,外包公司可以迅速提供所需的技术人员或调整团队规模。这种灵活性使成都企业能够更好地适应市场变化和业务需求的波动,同时减少了因人员调整而产生的额外成本和风险。

4、专注核心业务
通过将IT外包给专业供应商,企业可以将更多的精力和资源集中在核心业务上。外包公司负责提供和维护IT基础设施和系统,解决技术问题,管理安全和风险等任务,使企业能够专注于自身业务的发展和创新,这种专注有助于提高公司的生产力和竞争力。

公司企业选择IT外包相对于招聘IT人员具有明显的优势。通过降低成本、获得专业知识和技术、灵活性和可扩展性以及专注核心业务,使用外包服务可让公司能够获得更高的效率、更好的质量和更大的竞争优势。然而,每个公司的情况不同,对于如何选择IT外包还需综合考虑自身需求和外包供应商的实力,来选择适合自己的IT外包服务商。

软件公司员工技能矩阵

软件开发公司研发部各岗位员工技能矩阵。项目经理,需求分析师,设计工程师,数据库工程师,开发工程师,测试工程师,培训专员。

岗位名称:项目经理
岗位类型:管理

岗位职责描述:
完成项目的整体组织与实施,协调与控制;
对项目的各种风险进行评估,制定相应规避和控制措施;
制定项目的主体计划和各类子计划(时间规划、成本规划、资源规划、测试规划)等;
实时掌握项目的各种计划执行情况,控制项目的进度,分析、解决项目中的各种问题;
组织、实施对项目的各阶段成果物进行评审;
组织项目各个阶段会议、客户沟通会议、技术会议、评审会议等;
保持和客户的需求沟通、商务沟通,处理和解决与客户之间各种分歧;
保持组织内的上下级之间的沟通,及时向项目干系人通报项目的进展情况、风险状况、当前课题等;
组织本项目课题的预研、项目业务、技术的培训等;
对项目中的各种资源(人员、软硬件等)进行管理。

岗位名称:需求分析师
岗位类型:业务

岗位职责描述:
负责对客户需求进行调研及分析,能够与客户迅速建立沟通;
获取用户的目的、想法,将业务需求转化为软件系统需求,编写以准确逻辑分析为基础的详细需求规格说明书;
协助项目经理、系统架构师、系统分析师.开发人员理解需求;
系统规划,与产品人员进行前期调研和产品设计工作,编写调研报告和项目解决方案;
配合测试人员完成测试计划.测试用例.测试报告的编写;
能熟练使用Axure/Viio/Word/Excel/PPT/Xmind等软件编写需求分析文档及设计界面原型.画流程图等;
系统知识库体系的积淀与整理,最佳实践的总结与分析,组织培训等。

岗位名称:设计工程师
岗位类型:设计

岗位职责描述:
负责系统架构的整体规划;
对开发模型、开发方法、开发平台、数据组织结构等进行决策;
对系统的运行环境、软硬件、数据库支持等进行规划决策;
对系统的子系统/模块划分,功能设计、接口设计、网络结构、数据库等进行决策;
对系统的进程、并发、异常处理等运行期属性进行决策;
对系统的用户交互、客户满意度等属性进行决策;
对系统的可扩展性、可维护性、安全性、健壮性等质量属性进行决策。
负责系统的功能设计;
对程序员进行功能设计的说明和培训;
对程序员开发中进行技术指导。
对开发模型、开发方法、开发平台、数据组织结构等进行决策;
对系统的运行环境、软硬件、数据库支持等进行规划决策;
对系统的子系统/模块划分,功能设计、接口设计、网络结构、数据库等进行决策。

岗位名称:数据库工程师
岗位类型:设计/开发

岗位职责描述:
数据库的日常管理维护,包括数据库的备份、恢复、数据整理、日志分析、解决突发和疑难问题;
数据库性能分析及其优化,及时发现需要改进的数据库查询及其其他执行代码;
进行数据库的安装与部署,保证符合数据库安装部署的合理性、高效性;
进行数据库设计,数据库对象的开发,指导并审查开发人员业务数据层(DAO、数据连接、连接池、事务处理等)的构建工作;
负责有关数据库技术文档的编写、数据库技术预研、数据库技术培训;
协助软件工程师进行数据库产品选型、采购决策等。
协助项目经理完成项目的配置管理工作。
参加设计类、数据库操作类培训。

岗位名称:开发工程师
岗位类型:开发
岗位职责描述:
根据系统设计的要求进行系统功能的编码、代码review等;
负责系统的单体测试工作,参与系统的集成测试、系统测试、验收测试;
系统用户手册、安装运行手册等开发文档的编写;
经常了解用户的意见和需求,不断完善软件功能,达到用户满意;
定期参加部门和项目组织的人员培训;
协助项目经理进行项目小组的管理(制作小组工作计划、进行进度控制、工作评审等);(中高级程序员职责);
参加研发类培训。

岗位名称:测试工程师
岗位类型:测试

岗位职责描述:
负责对系统进行测试内容的整体规划;
依据项目主体计划,制定测试详细计划;
编写有效的系统测试用例并执行测试;
负责进行测试数据准备、测试环境搭建、测试结果的分析、评审等;
指导开发/测试人员进行项目的单体测试、集成测试、系统测试工作;
安装、部署、维护测试工具。
对测试团队成员进行测试理论知识、测试技能、测试工具的培训;
参加测试类培训。

岗位名称:培训专员
岗位类型:支持

岗位职责描述:
负责公司内部新员工入职培训;
整理、汇总、归档各类培训资料,协助编制培训教材;
组织实施培训需求调查,制订年/季/月度培训计划;
组织实施培训,跟踪培训效果反馈,并建立和完善培训档案;
发掘公司内部培训资源,协助上级领导建立内部培训讲师队伍;
协调与外部培训供应商间的关系,维护培训渠道和培训资源。


人工智能时代的程序设计教学与课程设计

本文原发表于微信公众号“于仕琪”:《人工智能时代的程序设计教学与课程设计》,人工智能的发展为计算机的教学也带来了影响,来看看大学的计算机系的专家之言。

作者:于仕琪,郑锋,廖琪梅,田蕾
单位:南方科技大学计算机科学与工程系

摘要:随着人工智能的兴起,学生对编程的热情逐渐从 C/C++ 向 Python 迁移,对于计算机硬件体系结构的理解也呈现逐年下降的趋势。当前许多人工智能从业者做的是人工智能算法设计,但参与基础人工智能软件开发的相对较少。我们认为本科生教育中应该加强基础软件开发的教学,可利用学生对人工智能的热情,培养学生开发基础底层软件平台的能力。本文作者在多年教学中,面向人工智能时代的社会需求,将一门传统的程序设计课程 “C/C++ 程序设计”,逐年优化和改进成为一门包含多项内容的 “高级计算机程序设计” 课程。该课程通过引入开源项目作为案例,向学生传授 C 和 C++ 的独特优势,介绍多种 CPU 架构、GPU 编程、计算瓶颈分析、各种开发工具和新型 Rust 语言等内容。这些内容让学生更加深入地理解程序设计,提升了教学质量,课程受到了学生的广泛欢迎。

关键词:程序设计,人工智能,C,C++,Rust

导言

目前人工智能已经成为热门的方向,大量的科研和技术人员投入其中。在科研人员中,大部分人的工作是深度学习算法设计,即利用开源的 PyTorch 或其他深度学习训练库,设计不同的深度网络结构,并在各种数据集上进行训练和评估。而在人工智能框架开发方面,例如优化计算效率、发挥硬件性能方面只有相对较少的人员投入。从国家需求来讲,我们需要在人工智能软硬件的基础架构有人才和技术积累。

计算机和人工智能创新想法的验证需要程序设计,其中 C 和 C++ 语言是大部分基础软件的实现语言,有着重要的地位。C 和 C++ 编程语言因为语法复杂,很多学生即使进行了系统学习,仍然难以编写出稳定且少错的程序,尤其是其中的指针和内存管理机制,令无数学生困扰不已。随着人工智能的发展,Python 编程语言的使用率日益提升,有的学生认为学好 Python 就足够了。当学生的就业方向是网络开发或者移动应用开发时,会认为熟悉 Java 已经足够;如果就业方向是网页前端开发,会觉得 HTML/JavaScript 足够。这样的现状让很多学生缺乏学习 C/C++ 的热情,甚至质疑学习 C/C++ 的必要性。其实不然,从学生的职业规划来讲,深厚的计算机基础可以打破 “35 岁退休” 魔咒。如果只追技术热点而缺少深入理解,学生的职业发展难以达到较高的层次。

1. 目前面临的困难

1.1 学生需要精通多门编程语言

程序设计是计算机和人工智能等相关专业本科生的一项基本能力。随着技术发展和需求的拓宽,学生除了掌握 C 和 C++ 编程语言之外,还需要掌握 Java、Python 甚至 JavaScript 等语言。此外,一些新型的编程语言如 Rust 也展示了潜力,需要学生有所了解甚至需要掌握。这么多编程语言,在本科生教育中为每种语言开设相应的课程不太现实,因为这会挤压其他课程的课时,无法在培养方案中实施。

当前大部分学校将 C 语言作为本科教育的第一门编程语言,并辅以 C++ 或 Java 作为第二门编程语言课,也可能将 Python 作为选修课程。C 和 C++ 作为两门课,因这两门语言的基础语法有较大重合度,会面临内容重复的问题。如果在 C++ 课程中不讲 C 课程中的基础语法,那么只能讲 C++ 的高级特性,而 C++ 最近 20 年的技术路线非常激进,引入了大量的新特性,但学生缺乏工程基础,即使学了许多 C++ 新特性,也难以理解为何需要这些新特性,无法将之变成真正的知识并进行应用。

C 和 C++ 语言中的指针和内存管理是其精髓和特色,同时也是其难点,有大量的学生无法深入理解并掌握这个知识点。C 和 C++ 的语法规则因为历史原因有大量晦涩的知识点,不像 Java 那样直白简单。例如 C 和 C++ 中基本数据类型的变量默认不进行初始化,内存管理不善容易泄露或者重复释放,进而导致程序崩溃。这让学生的程序中有大量的 BUG,让学生的自信心严重受挫。要深层理解程序的运行,除了理解内存还需要理解 CPU 缓存和寄存器。行业的发展需要学生精通多门编程语言,但现实却并非如此,大部分学生连一门编程语言都无法精通。

1.2 人工智能时代 C/C++ 的不可替代性

因为学习 C 和 C++ 语言面临诸多困难,有些学生甚至认为现在进入人工智能时代了,会用 Python 就足够了,甚至不会编程也可以,因为有 ChatGPT 这类工具自动生成代码。深度学习的热潮带来 Python 语言热,Python 语言因为易于实现想法,所以广泛应用于深度学习的训练和部署中。如果我们 “近距离” 看一下人工智能基础软件,便会发现 Python 主要应用在算法设计和部署中,底层软件如 PyTorch 和 TensorFlow 都是用 C 或 C++ 实现,编译成高效率的 Python 包供调用。如果底层硬件是英伟达的 GPU,那么需要调用 CUDA 库实现各种算法;如果底层是华为的昇腾 GPU,那么需要用华为的 Ascend C 接口和相关编译器。

使用最广泛的操作系统 Linux 是使用 C 语言开发,编译器 GCC 是 C 语言,数据库管理系统如 MySQL 和 PostgreSQL 是 C 语言,开源计算机视觉库 OpenCV 是 C++ 语言。也就是说,目前大部分计算机领域的基础软件是采用 C 或 C++ 实现。虽然新型的编程语言 Rust 很有前景,但尚未成广泛生态。在未来的若干年内,C 和 C++ 语言在计算机基础软件开发中依然不可替代。我们的人才培养,不仅需要培养人工智能算法设计方面的人才,更需要培养有着深厚计算机基础的系统开发人才。我们无法建立 “空中楼阁”,ChatGPT 的成功也凸显芯片、通信、计算机体系结构、编译器等基础开发愈发重要。

1.3 教学中的问题

在程序设计课程教学中,课程容易沦为 “语法规则课”,课程的目的容易降低为 “语法规则学习”。在这种低要求下,培养出的学生只会将简单代码输入 IDE(集成开发环境),然后点击 “运行” 按钮验证结果。学生不理解 “编译”“连接” 等基本编程概念,也缺乏开发大一点规模软件的经验。程序设计课程中的例子往往比较简单,可以帮助理解语法规则,但学了这些有什么用,必须使用一些真实的案例来回答。受限于高校教师一般缺乏工业界的工作经验,难以举出高质量的真实案例。

现代程序开发不仅涉及语法规则,还会涉及大量的工具软件和不同的开发平台,例如各种不同 IDE、各种编译器、Makefile/cmake 等代码管理工具、git 版本控制和合作工具等。较大比例的同学习惯于使用 Windows 操作系统的图形界面和笔记本电脑,缺少 DIY 桌面电脑带来的 CPU、内存、磁盘等硬件的直观概念,也无法理解有了易用的图形界面为什么要用命令行窗口输入命令。而计算机行业的现实是大部分开发,包括人工智能基础软件、网络服务器软件乃至手机 App 都是 Linux 或类 Unix 系统。

学生的学习动力也是一项挑战。学生会认为学习 Java、Python 和 JavaScript 跟就业市场更匹配,C 和 C++ 这类很难的编程语言是否有必要学习?我们在本科生教育中固然要帮助学生打好基础,深厚的基础方能让学生在职业生涯中 “以不变应万变”,破解 “35 岁退休” 问题,同时我们也必须考虑到学生的就业需求,让学生熟练使用各种开发工具,快速掌握一些 “容易” 的编程语言,平滑地进入职场。

2. 课程内容设计

本节介绍作者所在的南方科技大学的程序设计课程设计。南方科技大学本科生第一年不分专业,所有本科生都进行程序设计的通识教育。程序设计通识教育包括 Java、C、Python、Matlab 多门编程语言,学生根据自己的需求自由选择。如学生有意愿选择计算机专业,则需要修读 Java 编程语言。计算机专业将 Java 而非 C 作为第一门编程语言的原因在于 Java 的语法规则简单,入门相对容易,可以避免 C 语言带来的挫败感,重点培养学生的编程逻辑而非对语法细节的掌握,提升学生的编程兴趣。

学生在大学一年级学习的通识课程中的 Java 编程语言,目的是培养学生的逻辑思维能力,以及基础的编程能力,但这些基础能力无法应对人工智能时代对专业人才的要求。为了全面培养学生的程序设计能力,南科大计算机相关专业本科生的第二门程序设计课为 “高级计算机程序设计” 课程,面向人工智能时代的需求,经过多年迭代和改进而成。课程的所有课件和例程已经在 GitHub 网站开源 [1],课程视频在哔哩哔哩网站播放超过 20 万次 [2]。课程的上课安排为每星期 4 课时,其中理论课 2 课时,实验课 2 课时。理论课讲授语法知识和编程中的注意事项,实验课通过练习题让学生巩固理论课的知识点。

2.1 C 和 C++ 合并教学

鉴于修读本课程的学生已经有 Java 语言的程序设计基础,本课程将 C 和 C++ 两门编程语言合并起来一起讲授。C 和 C++ 两门语言的语法规则有一定程度的重合,但也有差异。两门语言一起讲授,既可以高效率地讲授相同的部分,避免分成两门课的衔接问题;也可以对两门语言中的差异进行对比,突出两门语言的不同,例如基础数据类型的变量的初始化,两门语言都不进行初始化;C++ 有函数重载,C 语言无函数重载;C 和 C++ 虽然都有结构体 struct,但二者的结构体是完全不同的。这样的对比学习,有助于学生对知识点掌握的更深刻。

两门语言都涉及指针和内存管理,这是课程的难点,也是重点。如果学生没有掌握指针和内存管理,那不算学过 C 和 C++ 语言。我们在课程中广泛采用内存示意图和动画对指针和内存这个知识点进行讲解,并将内存管理这个知识点渗透到课程中大部分知识点中。例如 C++ 中类的默认复制构造函数和赋值运算符的重载,都会涉及内存管理问题。课程对各种可能发生的指针和内存管理问题进行讲解和深入分析。

课程内容设计以 C 和 C++ 语言编程为基本内容,辅以介绍不同的 CPU 架构、GPU、Rust 语言以及各种开发工具,并引入开源软件作为案例,让学生在掌握基本知识点之上,尽可能地扩大知识面。课程的最终目的是加深学生对计算机的理解,提升学生动手能力,并开阔学生的眼界。

2.2 突出 C/C++ 的独特优势

课程设计的另一个目的是让学生体会到 C 和 C++ 语言的魅力,所以如何提升程序的效率是课程的重点内容之一。课程内容会涉及编译器不同编译选项(如 gcc 的 O3)对程序速度的影响,单指令多数据(SIMD)的用法,OpenMP 充分利用 CPU 多核心等。这些程序优化方法可以让程序有几十倍的速度提升,让学生亲身感受到 C 和 C++ 语言的价值。

课程将矩阵乘法的实现作为核心例子。矩阵乘法的原理在 “线性代数” 课程中有讲解,是现在深度学习领域中最核心的计算,无论普通卷积还是 Transformer 中的 Attention 模块,都是矩阵乘法计算。矩阵乘法的计算量在大多数深度学习模型中占 90% 以上的比重。矩阵乘法的基本实现很简单,只需要不到 10 行代码,但将之优化以提升速度却不容易。学生除了使用编译器优化选项、SIMD 和多核并行,还需要考虑充分利用 CPU 的缓存(Cache),提升缓存读取的命中率等一系列方法。矩阵乘法的例子可以让学生充分感受到程序效率跟计算机体系结构密切相关,再将自己的程序跟跟专业的矩阵计算库(如 OpenBLAS)进行对比,学生会深刻感受到自己的程序与专业程序的差距,这有助于提升学生对计算机的理解。

2.3 介绍 X86、ARM 和 RISC-V 架构

为了让学生充分理解不同 CPU 架构的相同和不同之处,课程中介绍了三种最常见 CPU 架构 X86、ARM 和 RISC-V 的异同。课程还提供了 ARM 开发板和华为的 ARM 云服务器供学生使用,并建议学生在 ARM 平台上完成课程项目。通过接触 ARM 系统,学生可以消除对 ARM 的陌生感,感受到使用 ARM 进行开发与在其他 CPU 架构上开发并无明显区别,消除学生畏难情绪。随着 RISC-V 硬件的日渐丰富,未来课程也会为学生提供 RISC-V 的开发环境。

课程还介绍了不同 CPU 架构中的 SIMD 指令,并讲解 SIMD 指令加速程序效率的作用。并鼓励学生使用 SIMD 指令(X86 的 AVX2 或者 ARM 的 NEON)去加速矩阵乘法的计算。通过使用 SIMD,学生不仅可以学会编程技巧,还可以更好的理解 CPU 中的寄存器和计算机制。

2.4 介绍 CUDA/Ascend C 编程

现有的各种人工智能基础软件基本上都是运行在专用的 GPU 上,要发挥 GPU 的计算能力则需要专用的第三方库,如英伟达的 CUDA 和华为的 Ascend C。课程中增加了 CUDA 和 Ascend C 的入门知识,通过在 GPU 上进行简单的矩阵运算,让学生了解 CUDA 和 Ascend C 的基本用法。

考虑到不是所有的学生都有 GPU 服务器,学校专门为本课程配置了一台有 GPU 的服务器,供学生远程登录使用。这部分内容仅是入门知识,目的不是让学生精通 GPU 编程,而是让学生有所了解并体验硬件加速的能力,看到相关名词不会产生畏惧心理。未来有需要的时候,学生可以快速上手 GPU 开发,为学生未来的发展提供知识储备。

2.5 介绍 Rust 编程语言

Rust 是一门新型的编程语言,是一门对内存安全的语言。目前 Linux 内核开发 [3] 和 Windows 开发 [4] 都支持 Rust 语言。Rust 语言未来有替换 C 和 C++ 语言的潜力,但 Rust 生态尚未成熟,专门开设一门课讲授 Rust 未必妥当。鉴于此,本课程在 C 和 C++ 内容之外,增加了 Rust 入门知识。相信学生在精通了 C 和 C++ 之后,通过课堂介绍的 Rust 入门知识,可以通过自学快速地掌握 Rust 语言。

2.6 介绍各种开发工具的用法

要形成真正的开发能力,除了掌握编程语言的相关知识,还需要熟练使用各种开发工具。鉴于理论课的内容已经很多,以下工具软件的学习放在课程的实验课中。这些软件的使用跟实验课练习一起考核,计入学生的考核成绩。

1. Linux 基本用法和常用命令:建议学生使用 Linux 系统开发。如果学生的个人电脑是 Windows,则建议安装 WSL(Windows Subsystem for Linux)。这样学生可以快速了解 Linux 的使用,特别是常用命令的使用。

2. gcc 和 g++ 编译器的用法:主要介绍一些常用选项,如 “-c”、“-o”、“-O3”。学生通过在命令行中使用编译器进行编译,强化对 “编译”、“连接” 和 “运行” 等概念的认知。

3. Makefile 的用法:通过一个包含多个源文件的项目,介绍 Makefile 的对多文件的管理。学生也可以通过 Makefile 理解多个源文件之间的关系。

4. cmake 的用法:针对跨平台源文件管理,介绍 Makefile 的不足,引出 cmake 的必要性。

5. VS Code IDE 的用法。介绍 IDE 将所有开发工具集中在一起的便利性。

6. git 的用法:通过代码的频繁修改会覆盖修改历史,进而引出版本管理的重要性,以及多人合作中的版本控制问题。

2.7 将开源项目引入课程

鉴于企业中的真实案例很难公开获得并引入课堂,可以将优秀的开源项目引入课堂,作为教学案例。本课程引入了两个开源项目,OpenCV [5] 和 OpenBLAS [6]。OpenCV 是一个开源计算机视觉库,采用 C++ 语言写成。课程引入 OpenCV 的代码,展示函数重载、运算符重载、内存管理、类模板等知识点在 OpenCV 中的实现。这让学生在简单的小例子之外,还能看到真实的大型项目代码,并从中学习。真实案例中也会存在不完美的设计,例如 OpenCV 中采用 int 类型标识矩阵的行数和列数,而非更合理的 size_t 类型,这是 OpenCV 的一个设计缺陷。通过介绍 OpenCV 20 多年的历史,来阐述这个缺陷的来源,并阐述软件在长期演进过程中为了兼容老版本,不得不做出的一些妥协,设计很难完美。真实项目如同一个小社会,虽然里面存在着很多不足,但项目都发挥着举足轻重的作用,要正确且全面的看待一个大型项目。

另一个开源项目 OpenBLAS 是 BLAS 和 LAPACK API 的开源实现,由张先轶博士领导开发,现已被广泛应用于很多基础软件中。在本课程中建议学生将自己实现的矩阵乘法与 OpenBLAS 的实现进行效率方面的对比,并鼓励学生设计尽可能逼近 OpenBLAS 性能的矩阵乘法。在对比中,OpenBLAS 在性能上会以绝对优势领先学生的实现,这会激起学生的好奇心,进而去了解程序加速机制。学生通过一番努力如能达到 OpenBLAS 速度的 1/4,则已经是非常优秀;也有极个别优秀学生达到 OpenBLAS 速度的 70%。这些开源项目引导学生向更深处探索,可避免优秀学生 “吃不饱” 的问题。

声明:本文原发表于微信公众号 “于仕琪”,可以转载,但不可修改。

3. 课程考核方式

好的考核手段,不仅可以有助于学生掌握基础知识,还可以激发学生对知识的兴趣。本课程的课程考核分有 “小测验”、“练习题”、“期末考试” 和 “课程项目” 四部分。

1. 小测验:小测验每周一次,理论课课程结束时在线上进行。小测验的内容为理论课课堂的主要知识点,通过大约 10 道有一定难度的客观题,测试学生对知识点的掌握。小测验必须在课程结束后 30 分钟内完成,逾期无分数。这种方式可以督促学生上课认真听讲。

2. 练习题:练习题在每周的实验课上完成,每次大约 2-5 道题,代码量大约 50-100 行。实验课上教师会简单讲一下练习题的要点,学生需要当堂完成练习,实验课上没有完成练习则无法得到分数。为了督促学生每周必须掌握本周知识点,不可拖延,所以练习题必须下课前完成,如完不成则没有分数,这可以避免学生将练习拖延到期末才做。

3. 期末考试:期末考试考核课程的关键知识点,如指针的应用,变量的初始化,类的各种特性等。期末考试的目的是让学生综合的复习和巩固课程中的重要知识点。

4. 课程项目:课程项目考核分数占比 65%,是学生花精力最多的部分。课程项目一般有 5 个,大约 3 个星期 1 个项目。项目包括 “简单计算器”,“C 程序与 Java 程序在矩阵乘法效率的对比”,“高效率矩阵乘法实现”,“通用的矩阵类实现”,“卷积神经网络前向计算实现” 等。考虑到本课程的重点是程序设计,而非软件工程,所以课程项目为单人项目,考核学生个人的综合开发能力。项目代码的评测不采用 OJ 自动判分系统,避免学生只考虑 OJ 系统考核的指标。项目的评分采用主观评分,由教师根据代码质量和项目报告质量综合考虑评分,引导学生以开发真正的工程项目为指导思想进行设计和开发。

4. 教学效果

4.1 课程受到学生欢迎

学生对课程的评教结果虽然不能直接评估课程的质量,但可以用来评估课程是否受学生欢迎。学生喜欢一门课,则会有比较高的评教分数。学生虽然不容易在本课程取得高分,但在过去两个学期分别获得 94.00 和 94.43 的评教得分。课程评教得分位列南科大计算机系课程的前 1/3。学生在评语中普遍给出了非常正面的评价,同时也表达了他们对这门课程的喜爱。
4.2 学生参与开源项目开发进一步提升能力

学生在课程学习中,通过接触真正的项目代码,并通过课程项目的锻炼,有了一定的编程经验。一些学生学完课程后,不再畏惧真正的开发,参与了 OpenCV 项目开发。在过去 3 年中,共有 22 位同学为 OpenCV 贡献了条形码解码、二维码解码、三维点云降采样、三维点云压缩、三维点云平面检测、深度学习算子实现、深度模型量化、图像格式解码等 10 个算法和功能。

在 OpenCV 开发中,学生需要更加深入的理解 OpenCV 代码,熟悉代码编写规范。代码提交之后,开源社区的审核者会对代码提出很多修改意见,学生需要跟社区内的多位相关开发人员交流,并优化和改进自己的代码。通过参与真实的开发,学生提升了技术能力和交流能力,而且还提升了对真实项目的理解。

5. 未来挑战

随着 ChatGPT 等人工智能工具的出现,有越来越多的学生采用软件辅助来写项目代码和写项目报告。本课程规则中允许学生使用此类工具,但需要在报告中予以清晰的标注和说明。但在批阅中,教师难以准确评估使用软件的比例。例如代码大部分使用了软件生成,还是小部分?只是用软件辅助提供思路,还是以软件为主生成代码?软件辅助是提升了学生的能力,还是提升了作弊的能力?目前能准确评估学生的方式之一是教师和学生一对一面谈,教师对代码提问,学生对问题作答。但这种评估方式在学生人数多的情况下非常难以实施。随着人工智能技术的快速发展,传统的考核方式将会面临越来越多的挑战,相应的教学和考核方式应该随之改变。
致谢

本课程受 “广东省在线开放课程” 和 “教育部 - 华为智能基座产教融合协同育人” 项目资助。

参考文献

[1].课程资料 Github 站点

[2].课程授课视频 哔哩哔哩站点

[3]."Adding support for the Rust language to the Linux kernel."

[4].Claburn, Thomas (2023-04-27). "Microsoft is rewriting core Windows libraries in Rust".

[5].开源项目 OpenCV

[6].开源项目 OpenBLAS