《代码中的软件工程》学习总结

参考资料《代码中的软件工程》代码中的软件工程: 《代码中的软件工程》一书的配套ppt和源代码

本学期学习了孟宁老师的高级软件工程这门课程,并结合《代码中的软件工程》在课程中进行学习与实践,受益匪浅,收获良多,让我深刻理解到了软件工程的意义所在。

这门课兼具理论与实践,内容丰富、全面、与时俱进,从一个更高的层面加深了我对软件工程的认知,软件工程师不仅仅只是写代码,更需要从各个角度把握软件开发,比如从用户角度、产品角度、管理角度等。这门课程通过对menu案例的逐步深入,从初始版本到增加LinkTable,再到之后的细化,在不断重构和迭代的过程中,我学习到了很多实用的知识,实际理解了软件接口,以及高内聚低耦合的意义,让我深刻认识到软件开发过程中需要注意的事项。下面从以下几个方面对高级软件工程这门课做学习总结。

目录

工欲善其事 必先利其器

VS Code

git

Vim

正则化

代码中的软件工程

代码风格的原则:

模块化软件设计

接口

可重入函数与线程安全

从需求分析到软件设计

什么是需求分析

为什么需求重要

获取高质量需求的方法

高质量需求的特点

需求类型

需求分析的两类基本方法

用例满足的四个必要条件

用例的抽象层级

用例建模的基本步骤

统一过程的核心要义

敏捷统一过程的四个关键步骤

形成软件设计方案的基本方法

软件科学基础概论

软件中的一些特殊机制

设计模式

常见的软件架构举例

软件架构的描述方法

软件过程模型

团队强度和项目特点的关系

团队项目的基本策略

CMM/CMMI

敏捷宣言的核心思想

DevOps

课程总结


工欲善其事 必先利其器

VS Code

优点:

  • 简洁而聚焦的产品定位,贯穿始终
  • 进程隔离的插件模型
  • UI 渲染与业务逻辑隔离,一致的用户体验
  • 代码理解和调试——LSP和DAP两大协议
  • 集大成的 Remote Development

VS Code专注于开发者“最常用”的功能:编辑器+代码理解+版本控制+远程开发+调试。这是一个非常节制而平衡的选择。

有以下常用快捷键:Ctrl+Shift+P调出VS Code命令行,Ctrl+~ 调出VS Code的终端,Ctrl/⌘+Shift+E 文件资源管理器,Ctrl/⌘+Shift+G 源代码管理,Ctrl/⌘+Shift+F 跨文件搜索,Ctrl/⌘+Shift+D 启动和调试,Ctrl/⌘+Shift+M 查看错误和警告,Ctrl/⌘+Shift+X 管理扩展插件。

git

  • 独立文件方式,比如另存为
  • 补丁方式,比如diff(只有这两种版本控制方式)
  • 中心版本控制系统,比如Concurrent Versions System/cvs和Subversion/svn
  • 分布式版本控制系统,比如Git,是目前世界上最先进的分布式版本控制系统(没有之一)(利于社区化)

常用git命令以及相关知识点如下:

  • 可能有多个branch,至少有一个叫master
  • git init # 在一个新建的目录下创建版本库
  • git clone https://github.com/YOUR_NAME/REPO_NAME.git # 通过clone远端的版本库从而在 本地创建一个版本库
  • git add [FILES] # 把文件添加到暂存区(Index)
  • git commit ­m “wrote a commit log infro” # 把暂存区里的文件提交到仓库
  • git log # 查看当前HEAD之前的提交记录,便于回到过去
  • git reset —hard HEAD^^/HEAD~100/commit­id/commit­id的头几个字符 # 回退
  • git reflog # 可以查看当前HEAD之后的提交记录,便于回到未来
  • git fetch命令官方的解释是“Download objects and refs from another repository”,即下载一个远程存储库数据对象等信息到本地存储库。
  • git push命令官方的解释是“Update remote refs along with associated objects”,即将本地存储 库的相关数据对象更新到远程存储库。
  • git merge命令官方的解释是“Join two or more development histories together”,即合并两个 或多个开发历史记录。
  • git pull命令官方的解释是“Fetch from and integrate with another repository or a local branch”,即从其他存储库或分支抓取并合并到当前存储库的当前分支。
  • 过去和未来之间的分界点就是HEAD,即当前工作区所依赖的版本。
  • line diff是形成增量补丁的技术方法,即一个文件按行对比(line diff)将差异的部分制作成一个增量补丁。
  • commit是存储到仓库里的一个版本,是整个项目范围内的一个或多个文件的增量补丁合并起来,形成项目的增量补丁,是一次提交记录。每个提交(commit)都生成一个唯一的commit ID。
  • branch是按时间线依次排列的一组提交记录(commit),理论上可以通过当前branch上最初的提交(commit)依次打补丁直到HEAD得到当前工作区里的源代码。
  • tag标签就是某次提交(commit)的commit ID的别名。
  • git checkout -­b mybranch 创建新的分支
  • git branch 切换分支
  • git rebase ­i [startpoint] [endpoint]
 #团队项目中的分叉合并
 git clone https://DOMAIN_NAME/YOUR_NAME/REPO_NAME.git
 git pull
 git checkout -b mybranch
 git branch
 git add FILES
 git commit -m "commit log"
 git checkout master
 git pull
 git merge --no-ff mybranch
 git push

Vim

  • 命令模式(Command mode), 用户刚刚启动vi /vim,便进入了命令模式。此状态下敲击键盘动作会被vim识别为命令,而非输入字符。比如我们此时按下i,并不会输入一个字符,i被当作了一个命令。命令模式只有一些最基本的命令,因此仍要依靠底线命令模式输入更多命令
  • 输入模式(Insert mode), 在命令模式下按下i就进入了输入模式,按ESC退出输入模式,切换到命令模式 。
  • 底线命令模式(Last line mode), 在命令模式下按下 :( 英文冒号)就进入了底线命令模式。底线命令模式可以输入单个或多个字符的命令,可用的命令非常多。基本的命令有q(退出程序 )、 w(保存文件)等。按ESC键可随时退出底线命令模式 。

正则化

作用(正则表达式是一种字符串匹配的模式)

  1. 数据验证:比如电话号码、邮箱等
  2. 替换文本:快速找到特定文本,用于替换
  3. 快速提取字符串:基于匹配原则,快速从文档中查找或提取子字符串

语法(普通字符+特殊字符)

普通字符

  1. [abc] 匹配[...]的所有字符
  2. [^abc] 取反,除了[...]的其他字符
  3. [A-Z] 区间字母A到Z
  4. . 匹配除(\n换行符 \r 回车符)的任何单个字符
  5. \s \S 匹配所有,\s所有空白符,包括换行 \S非空白符,不包括换行
  6. \w 匹配字母、数字、下划线

特殊字符

  1. $ 结尾位置 (匹配$字符----\$)
  2. ^ 开始位置(匹配$字符----\^)
  3. () 子表达式开始和结束(匹配字符----\(   和  \))
  4. * 匹配零次或多次(匹配字符----\*)
  5. + 匹配一次或多次(匹配字符----\+)
  6. ? 匹配零次或一次(匹配字符----\?)

代码中的软件工程

代码风格的原则:

简明、易读、无二义性。

到底什么样的代码是好代码呢?一是规范整洁。遵守常规语言规范,合理使用空格、空行、缩进、注释等;二是逻辑清晰。没有代码冗余、重复,让人清晰明了的命名规则。做到逻辑清晰不仅要求程序员的编程能力,更重要的是提高设计能力,选用合适的设计模式、软件架构风格可以有效改善代码的逻辑结构,会让代码简洁清晰;三是优雅。优雅的代码是设计的艺术,是编码的艺术,是编程的最高追求。

我们要掌握编写高质量代码的基本方法:

  1. 通过控制结构简化代码(if else/while/switch)
  2.  通过数据结构简化代码
  3. 一定要有错误处理
  4. 注意性能优先的代价
  5. 拒绝修修补补要不断重构代码

模块化软件设计

模块化软件设计(高内聚 低耦合)是在软件系统设计时保持系统内各部分相对独立,以便每一个部分可以被独立地进行设计和开发。一般我们使用耦合度(Coupling)和内聚度(Cohesion)来衡量软件模块化的程度:追求松散耦合,功能内聚(理想的内聚是功能内聚,也就是一个软件模块只做一件事,只完成一个主要功能点或者一个软件特性(Feather))

模块化设计的常见方法为KISS原则:

  • 一行代码只做一件事
  • 一个块代码只做一件事
  • 一个函数只做一件事
  • 一个软件模块只做一件事

使用本地化外部接口来提高代码的适应能力(隔离);另外,先写伪代码的代码结构更好一些。

接口

接口就是互相联系的双方共同遵守的一种协议规范,在我们软件系统内部一般的接口方式是通过定义一组API函数来约定软件模块之间的沟通方式。接口具体定义了软件模块对系统的其他部分提供了怎样的服务,以及系统的其他部分如何访问所提供的服务。

在面向过程的编程中,接口一般定义了数据结构及操作这些数据结构的函数;而在面向对象的编程中,接口是对象对外开放(public)的一组属性和方法的集合。函数或方法具体包括名称、参数和返回值等。
接口规格包含五个基本要素

  • 接口的目的;——函数的方法、名称
  • 接口使用前所需要满足的条件,一般称为前置条件或假定条件;
  • 使用接口的双方遵守的协议规范;
  • 接口使用之后的效果,一般称为后置条件;
  • 接口所隐含的质量属性。

接口与耦合度,一般可以分为紧密耦合(Tightly Coupled)、松散耦合(Loosely Coupled)和无耦合(Uncoupled)更细致地对耦合度进一步划分的话,耦合度依次递增可以分为无耦合、数据耦合、标记耦合、控制耦合、公共耦合和内容耦合。比如以下接口传递的可能是一个隐含的存储区或数据结构,肯定不是数据耦合。

int AddLinkTableNode(tLinkTable *pLinkTable, tLinkTableNode * pNode)

通用接口定义的基本方法

  1. 参数化上下文(使用参数信息,不依赖上下文环境,即不使用封闭闭包)
  2. 移除前置条件(sum函数中使用数组传递参数,不再限定参数个数)
  3. 简化后置条件(移除参数之间的关系,使sum返回的是数组全部元素的和)

可重入函数与线程安全

指令乱序(reorder)问题

程序的代码执行顺序有可能被编译器或CPU根据某种策略打乱指令执行顺序,目的是提升程序的执行性能,让程序的执行尽可能并行,这就是所谓指令乱序问题。

软件设计人员可以在正确的位置告诉编译器或CPU哪里可以允许指令乱序,哪里不能接受指令乱序,从而在保证软件正确性的同时允许编译或执行层面的性能优化。

指令乱序问题需要分为三个层次,第1层是多线程编程中的业务逻辑层面的函数可重入性和线程安全问题;第2层是编译器编译优化造成的指令乱序;第3层是CPU乱序执行指令的问题。

可重入函数(多线程)可以由多于一个任务并发使用,而不必担心数据错误。相反,不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用局部变量,要么在使用全局变量时保护自己的数据。

可重入函数的基本要求

  • 不为连续的调用持有静态数据;
  • 不返回指向静态数据的指针;
  • 所有数据都由函数的调用者提供;
  • 使用局部变量,或者通过制作全局数据的局部变量拷贝来保护全局数据;
  • 使用静态数据或全局变量时做周密的并行时序分析,通过临界区互斥避免临界区冲突;
  • 绝不调用任何不可重入函数。

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行读写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

函数的可重入性与线程安全之间的关系

可重入的函数不一定是线程安全的,可能是线程安全的也可能不是线程安全的;可重入的函数在多个线程中并发使用时是线程安全的,但不同的可重入函数(共享全局变量及静态变量)在多个线程中并发使用时会有线程安全问题;不可重入的函数一定不是线程安全的。

从需求分析到软件设计

什么是需求分析

需求分析是在获取需求的基础上进一步对软件涉及的对象或实体的状态、特征和行为进行准确描述或建模的工作

为什么需求重要

不完整的需求是导致项目失败的主要因素,需求过程的某些部分设计是大部分项目失败的原因,如果不尽早检测到需求错误,代价可能会很高

获取高质量需求的方法

采访利益相关者、回顾文档、研究当前系统、了解用户的任务、使用特定领域的策略、与当前和潜在用户进行头脑风暴

高质量需求的特点

Correct正确的、Consistent一致的、Unambigious无二义性的、Complete完整的、Feasible可行的、Relevant和主要目标相关、Testable可测试的、Traceable可追踪的

需求类型

  • 功能要求:根据所需活动描述所需行为
  • 质量要求或非功能性要求:描述软件必须具备的一些质量特征
  • 设计约束:设计决策,例如平台或接口组件的选择
  • 流程约束:对可用于构建系统的技术或资源的限制

需求分析的两类基本方法

  • 原型化方法:很好地整理出用户接口方式
  • 建模的方法:快速给出有关事件发生顺序或活动同步约束的问题,能够在逻辑上形成模型来整顿繁杂的需求细节

用例满足的四个必要条件

  • 是一个业务过程
  • 由某个参与者触发
  • 为特定的参与者完成一个特定的业务任务
  • 终止于某个特定参与者

用例的抽象层级

  • 抽象用例:一个干什么、做什么或完成什么业务任务的动名词短语
  • 高层用例:给用例的范围划定一个边界
  • 扩展用例:将参与者和待开发软件系统为了完成用例所规定的业务任务的交互过程详细地描述出来

用例建模的基本步骤

从需求表述中找出用例 描述用例开始和结束的状态 对用例按照子系统进行分类,描述用例与用例、用例与参与者之间的上下文关系 进一步逐一分析用例与参与者的详细交互过程

统一过程的核心要义

用例驱动、以架构为中心、增量且迭代的过程 敏捷统一过程进一步将软件过程中每一次迭代过程划分为计划阶段和增量阶段

敏捷统一过程的四个关键步骤

  1. 确定需求
  2. 通过用例的方式来满足这些需求
  3. 分配这些用例到各增量阶段
  4. 具体完成各增量阶段所计划的任务

形成软件设计方案的基本方法

分析是分解大问题变成易于理解的小问题 综合是将小问题的解决方案组合起来构建软件的整体解决方案。

软件科学基础概论

软件中的一些特殊机制

  • 回调函数:回调函数就是一个通过函数指针调用的函数。把函数的指针(地址)作为参数传递给另一个函数,当这个指针调用其所指向的函数时,就称这是回调函数。回调函数不是该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应
  • 继承和组合:继承可以重用代码,但是破坏了代码的封装特性,增加了父类与子类之间的代码模块耦合,因此我们需要避免使用继承,在大多数情况下,应该使用对象组合替代继承来实现相同的目标
  • 多态:允许将不同的子类类型的对象动态赋值给父类类型的变量,通过父类的变量调用方法在执行时实际执行的可能是不同的子类对象方法,因而表现出不同的执行效果
  • 闭包:闭包是变量作用域的一种特殊情形,一般用在将函数作为返回值时,该函数执行所需的上下文环境也作为返回的函数对象的一部分,这样该函数对象就是一个闭包 函数和对其周围状态的引用捆绑在一起构成闭包,在JavaScript中,每当函数被创建,就会在函数生成时生成闭包
  • 异步调用:Promise对象实际上是对回调函数机制的封装,也就是通过then方法定义的函数与resolve/reject函数绑定,简化了回调函数传入的接口实现,在逻辑上也更加通顺,看起来像是个同步接口
  • 匿名函数:lamda函数是函数式编程中的高阶函数,在我们常见的命令式编程语言中常常以匿名函数的形式出现,比如无参数的代码块{ code },有参数的匿名函数往往会使用箭头函数 { x => code };

设计模式

本质是面向对象设计原则的实际运用总结出的经验模型,用模块化来包容变化,使用模块化封装的方法,按照模块化追求的高内聚低耦合目标,借助于抽象思维对模块内部信息的隐藏并使用封装接口对外只暴露必要的可见信息。有如下主要设计原则:


1、开闭原则(Open Closed Principle,OCP)软件应当对扩展开放,对修改关闭(Software entities should be open for extension,but closed for modification)
2、Liskov替换原则(Liskov Substitution Principle,LSP)继承必须确保超类所拥有的性质在子类中仍然成立(Inheritance should ensure that any property proved about supertype objects also holds for subtype objects)。
3、依赖倒置原则(Dependence Inversion Principle,DIP)高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象(High level modules shouldnot depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details. Details should depend upon abstractions)。其核心思想是:要面向接口编程,不要面向实现编程。
4、单一职责原则(Single Responsibility Principle,SRP)单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分(There should never be more than one reason for a class to change)
5、迪米特法则(Law of Demeter,LoD)如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性
6、合成复用原则(Composite Reuse Principle,CRP)它要求在软件复用时,要尽量先使用组合或者聚合关系来实现,其次才考虑使用继承关系来实现


创建型模式

  • 单例模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例
  • 原型模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例,原型模式的应用场景非常多,几乎所有通过复制的方式创建新实例的场景都有原型模式
  • 建造者模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象

结构型模式

  • 代理模式:为某对象提供一种代理以控制对该对象的访问,从而限制、增强或修改该对象的一些特性。典型的应用如外部接口本地化将外部的输入和输出封装成本地接口,有效降低模块与外部的耦合度。
  • 适配器模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作,对象组合方式的适配器模式比较常用。

行为型模式

  • 策略模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。策略模式是多态和对象组合的综合应用
  • 命令模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开
  • 模板方法模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
  • 职责链模式:将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链
  • 中介者模式:定义一个中介对象来简化原有对象之间的交互关系 降低系统中对象间的耦合度,使原有对象之间不必相互了解。如控制器就是模型和视图的中介者,采用“中介者模式”大大降低了对象之间的耦合性,提高系统的灵活性

常见的软件架构举例

三层架构

表示层:主要对用户的请求接受,以及数据的返回,为客户端提供应用程序的访问。
业务逻辑层:主要负责对数据层的操作。也就是说把一些数据层的操作进行组合。
数据访问层:主要看数据层里面有没有包含逻辑处理,实际上它的各个函数主要完成各个对数据文件的操作。而不必管其他操作。

MVC

Model代表一个存取数据的对象及其数据模型 View代表模型包含的数据的表达方式,一般表达为可视化的界面接口 Controller作用于模型和视图上,控制数据流向模型对象,并在数据变化时更新视图。控制器可以使视图与模型分离开解耦合

MVVM

把Model用纯JavaScript对象表示,View负责显示,两者做到了最大限度的分离。把Model和View关联起来的就是ViewModel。ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model

软件架构的描述方法

分解视图:一般分解视图呈现为较为明晰的分解结构(breakdown structure)特点。分解视图用软件模块勾划出系统结构,往往会通过不同抽象层级的软件模块形成层次化的结构。 
依赖视图:依赖视图展现了软件模块之间的依赖关系。比如一个软件模块A调用了另一个软件模块B,那么我们说软件模块A直接依赖软件模块B。
泛化视图:展现了软件模块之间的一般化或具体化的关系,典型的例子就是面向对象分析和设计方法中类之间的继承关系。
执行视图:执行视图展示了系统运行时的时序结构特点,比如流程图、时序图等。执行视图中的每一个执行实体,一般称为组件(Component),都是不同于其他组件的执行实体。
实现视图:实现视图是描述软件架构与源文件之间的映射关系。
部署视图:部署视图是将执行实体和计算机资源建立映射关系。
工作分配视图:工作分配视图将系统分解成可独立完成的工作任务,以便分配给各项目团队和成员。

软件过程模型

瀑布模型是一个过程活动的顺序结构,没有任何迭代。瀑布模型不能为处理开发过程中的变更提供任何指导意义,因为瀑布模型假定需求不会发生任何变化

原型化的瀑布模型,原型就是根据需要完成的软件的一部分,完成哪一部分是根据开发原型的目标确定,比较常见的有用户接口原型和软件架构原型

v模型在瀑布模型基础上将需求分析和接受度测试、系统设计和系统测试、详细设计和单元测试结合起来,最后是产品,提高过程活动的内聚度。生死相依原则指创建一个对象和销毁一个对象的代码成对出现便于代码的组织和管理,V模型就是开始一个特定过程活动和评估该特定过程活动成对出现,从而便于软件开发过程的组织和管理

螺旋模型是一种演化软件开发过程模型,兼顾了快速原型的迭代的特征以及瀑布模型的系统化与严格监控。螺旋模型最大的特点在于引入了其他模型不具备的风险管理,使软件在无法排除重大风险时有机会停止,以减小损失

团队强度和项目特点的关系

项目失败最根本的原因是团队问题(缺乏有效的领导和管理、不能做出妥协、安排或不善于合作、缺少参与、拖拉与缺乏信息、质量低劣、功能多余、无效的组员互评)

团队项目的基本策略

计划先行 完成概念设计 选择开发策略 完成规模计算 完成时间计算 评估风险 建立策略文档 指定配置管理计划

CMM/CMMI

CMM/CMMI用于评价软件生成能力并帮助其改善软件质量的方法,成为了评估软件能力与成熟度的一套标准,它侧重于软件开发过程的管理及工程能力的提高与评估 CMM有两种通用的评估方法用以评估组织软件过程的成熟度:软件过程评估和软件能力评价 CMM主要应用在两个方面:能力评估(软件过程评估和软件能力评估)和过程改进

敏捷宣言的核心思想

个体和互动高于流程和工具;工作的软件高于详尽的文档;客户合作高于合同谈判;响应变化高于遵循计划

DevOps

DevOps是一组过程、方法与系统的统称,用于促进软件开发、技术运营和质量保障部门之间的沟通、协作与整合

课程总结

《代码中的软件工程》通过对软件开发过程中常见的问题和解决方案进行深入浅出的讲解,为软件工程师提供了很多实用的知识和经验。在学习这本书的过程中,我得到了很多启示和收获:代码的可读性非常重要。代码不仅要能够正确地运行,还要易于理解和维护。在编写代码时,要注意代码的命名、注释、缩进等方面,使其易于阅读和理解。代码的可测试性也非常重要。软件开发是一个持续迭代的过程,只有能够快速、准确地测试代码,才能保证软件质量和开发效率。在编写代码时,要注意代码的结构和逻辑,使其易于进行单元测试和集成测试。设计模式是提高代码质量和可维护性的重要手段。设计模式是一种被广泛认可的解决特定问题的方案,可以帮助软件工程师更好地组织和管理代码。在实际开发中,要积极应用各种设计模式,提高代码的可复用性和可维护性。持续集成和持续部署是软件开发中不可或缺的环节。持续集成是指频繁地将代码集成到主干分支中,并进行自动化测试和构建,以确保代码的质量和稳定性。持续部署是指自动化部署代码到生产环境中,以提高开发效率和响应速度。在实际开发中,要积极应用持续集成和持续部署技术,提高开发效率和软件质量。团队合作和沟通非常重要。软件开发是一个团队合作的过程,只有团队成员之间的沟通和协作良好,才能保证项目的顺利进行和成功实现。在实际开发中,要积极与团队成员沟通,及时解决问题,确保项目进度和质量。

总之,《代码中的软件工程》一书涵盖了软件开发过程中的各个方面,从代码编写到测试、部署和团队协作,都给出了实用的建议和经验。我认为这本书是必读的,它可以帮助我们更好地理解软件开发的本质和要点,提高开发效率和软件质量,为我们的职业发展提供有力的支持。

学号504

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
网页制作代码+课程总结;旅游网站 CSS代码@charset "utf-8"; body { font-size: 12px; color: #666; text-align: center; margin: 0px; padding: 0px; } #container{ text-align:left; padding:0px; width:1400px; position:relative; margin-top:0px; margin-right:auto; margin-bottom:0px; margin-left:auto; } #top{ padding-top:15px; height:90px; } #logo{ margin:0px; padding:0px; float:left; width:365px; text-align:right; } .pic{ vertical-align:middle; padding-right:20px; } #ss{ float:left; width:835px; text-align:right; height:35px; padding-top:0px; padding-right:200px; padding-bottom:0px; padding-left:0px; margin:0px; } #daohang{ margin-top:5px; margin-right:0px; margin-bottom:0px; margin-left:195px; float:left; text-align:right; padding:0px; height:38px; width:840px; } #daohang ul{ margin:0px; padding:0px; list-style:none; } #daohang ul li{ text-align:center; float:left; width:90px; } #daohang a{ background-image:url(images/bj01.jpg); display:block; margin:0px; padding-top:10px; padding-right:0px; padding-bottom:13px; padding-left:0px; } #daohang a:link,#daohang a:visited{ color:#FFF; text-decoration:none; } #daohang a:hover{ color:#333; text-decoration:underline; } #banner{ background-image:url(images/pic.jpg); background-repeat:no-repeat; height:160px; text-align:center; padding-top:400px; padding-right:0px; padding-bottom:0px; padding-left:0px; margin:0px; } #banner img{ padding:0px 5px 0px 5px;} .daohang { font-size: 16px; color: #09F; background-color: #060; text-align: center; display: block; } #footer{ color: #000; background-image: url(images/bj03.jpg); background-repeat: repeat-x; text-align: center; padding: 30px 0px 29px 0px; font-size: 16px; }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值