软件工程理论与实践第8期-8 系统实现

8 系统实现

8-1 编程语言

第1关:选择正确的编程语言

任务描述
本关任务:根据题目中所给的软件工程项目描述,选择最合适的编程语言。

相关知识
编程语言选择标准:
① 性能要求。
如果所开发的软件的对性能有很高的要求,或者计算开销较大,那么最好使用静态的、可编译的语言,如C#,Java等。
② 用户的要求。
如果是用户来负责维护将来开发出来的软件,则用户一般要求使用他们熟悉的语言编写程序。
③ 软件的运行平台。
软件的运行平台往往限制了编程语言的选择,如果目标运行平台不支持你想要的语言,那么你就不能使用它,如在嵌入式编程中选择51单片机,则不能使用Python语言。
④ 程序员的知识。
在科学合理的原则下,我们最好选择开发人员熟悉的编程语言。如果开发人员对某一种语言比较熟悉,就可以很好地预测开发时间、开发过程等内容,避免大的未知变数,提高开发效率。
⑤ 软件的可移植性要求。
如果将来开发的软件要在不同系统平台运行,就要使用可移植性好的语言,如Java、Golang等。
⑥ 软件的应用领域。
各种语言都有自己的适用范围,通用设计语言并不是适用于所有领域,如嵌入式平台、IOS平台。有许多特定的领域有自己的专用语言。比如:财务分析、文本解析、科学计算、实时领域、服务器、人工智能等。使用领域的专用语言可以减少很多工作量,提高编程效率。

8-2 编程风格

第1关:命名

1 任务描述
本关任务:本关考察学生对命名规范的掌握,包括命名基本要求、驼峰命名法、禁止拼音命名和不规范的缩写、命名拆分等。学生首先学习相关知识,然后进行实践以加深对知识的掌握。

2 相关知识
2.1 命名基本要求

(1)不能以下划线或美元符号开始或结束
(2)类名使用大驼峰(UpperCamelCase)风格
(3)方法名、参数名、成员变量、局部变量都统一使用小驼峰(lowerCamelCase)风格
(4)常量命名全部大写,单词间用下划线隔开
(5)包名统一使用小写

2.2 驼峰命名法
驼峰命名法近年来越来越流行,在java平台、Microsoft Windows以及许多新的函数库中使用得较多。正如它的名字,驼峰命名法指混合大小写字母来构成的名字,这样的命名看起来就像驼峰一样此起彼伏。骆驼式的命名法是一种惯例,并不是强制的,目的是增加可读性。
驼峰命名法分为小驼峰法和大驼峰法两种。小驼峰法是指第一个单词以小写字母开始,从第二个单词开始以后的每个单词的首字母都采用大写字母。变量名一般用小驼峰法书写。
与小驼峰法相比较,大驼峰法(即帕斯卡命名法)的第一个单词的首字母也是大写。常用于类名、属性、命名空间等。

2.3 禁止拼音命名
规范命名是为了统一标准,降低程序理解难度,提升程序可维护性,提高团队协作效率。拼音命名有如下这些缺陷,所以我们禁止拼音命名。

public class Pingyin {
    //一、拼音本身不能很好的区分动词和名词,
    //提交类型的实例应该是名词,而对于方法这个场景,“提交”应该是动词。但从拼音明明本身,无法得知是名词还是动词。
    TiJiao tijiao;
    
    void tiJiao(){
        
    }
    
    //二、从可读性来说,拼音的可读性不一定有英文高
    //比如以下场景,对比拼音和英文。拼音很长,阅读不方便,而英文一目了然。
    //获取指定时间后的所有订单
    Object huoQuZhiDingShiJianHuoDeSuoYouDingDan(LocalDateTime time){
        return null;
    }
    
    Object getOrdersAfter(LocalDateTime time){
        return null;
    }
    
    //三、拼音无法区分单数和复数
    //返回列表的提交应该是复数,返回单个提交,应该是单数,而拼音无法体现。
    List<TiJiao> huoQuTiJiao(){
        return null;
    }
    
    TiJiao huoQuTiJiao(){
        return null;
    }
    
    //对比使用英文单词命名,区分单复数很方便。
    List<TiJiao> getOrders(){
        return null;
    }
    
    TiJiao getOrder(){
        return null;
    }    
}
2.4 禁止不规范缩写
有时候我们想将一些长的命名进行缩写,减少命名长度,但这样会减少可读性。

public class Abbreviation{
    //一、不规范的缩写,影响可读性。
    //generateId的缩写
    void genId(){
        
    }
    //startActivity的缩写
    void startAct(){
        
    }
    
    //可能是percent的缩写,也可能是personal computer的缩写
    private double pc;
    //二、通用的缩写,可以使用
    //如Tcp
    void createTcpConnection() {
        
    }
    //dns
    private String dnsServer;
    //k8s
    private String k8sService;
    
    // 专业领域
    public void dijkstra() {
    }
}
2.5 足够长以揭示意图, 又不过长难于理解
虽然第一个方法名将意图表达的很清楚(通过产品id从库存系统获取产品库存),但太过于冗长,在阅读时无法一眼看出方法的具体含义。

优化:方法的参数名为productId,已经表达了通过产品id获取的含义,所以可以去掉; 产品库存理应从库存系统获取,所以“从库存系统”的限定语也可以删除。

优化后,方法名表达的含义一目了然。

//通过产品id从库存系统获取产品库存
public void getProductInventoryFromInventorySystemByProductId(String productId) {
}
获取产品库存
public void getProductInventory(String productId) {
}
2.6 命名拆分
如果不好命名,考虑类/方法职责过多,是否需要拆分重构。

根据方法名可以得知,该方法需要检查用户是否存在、更新用户、更新缓存、发送邮件给用户,导致命名过长。

public void updateUserBothDBAndCacheThenSendMail() {
    // 检查用户是否存在
    // 更新用户
    // 更新缓存
    // 发送邮件给用户
}
进行拆分重构:

// 检查用户是否存在
public void isUserExisted() {
}
// 更新用户
public void updateUserInDB() {
}
// 更新缓存
public void updateUserInCache() {
}
// 发送邮件给用户
public void sendNotification() {
}
2.7 使用有意义的名字
(1)当前日期

// 反例
// 无法得知是什么日期
private LocalDate date;
// 正例
//可以一目了然看出这是当前日期
private LocalDate currentDate;
(2)每页数据条数

// 反例
private int size;
private int lines;
// 正例
private int pageSize;
private int linesPerPage;
(3)布尔值

// 反例
private boolean flag;
// 正例
private boolean dataReady;
(4)数值

// 反例
private static final int THREE = 3;
// 正例
private static final int RETRY_TIMES = 3;
(5)参数

// 反例
public void copyArray(int[] aArray, int[] bArray) {
    for (int i = 0; i < aArray.length; i++) {
        bArray[i] = aArray[i];
    }
}
// 正例
public void copy(int[] source, int[] target) {
    for (int sourceIndex = 0; sourceIndex < source.length; sourceIndex++) {
        target[sourceIndex] = source[sourceIndex];
    }
}
// 反例
public void printScoreDemoOne() {
    int[][] scores = new int[10][5];
    for (int i = 0; i < scores.length; i++) {
        for (int j = 0; j < scores[i].length; j++) {
            int score = scores[i][j];
            // xxx
        }
    }
}
// 正例
public void printScoreDemoTwo() {
    int[][] scores = new int[10][5];
    for (int userIndex = 0; userIndex < scores.length; userIndex++) {
        for (int courseIndex = 0; courseIndex < scores[userIndex].length; courseIndex++) {
            int score = scores[userIndex][courseIndex];
            // xxx
        }
    }
}
2.8 范围越大命名越长;范围越小命名越短
(1)下面代码,定义了用户token所在缓存的命名空间,因为类名只限定了是命名空间,所以我们需要用比较多的限定来限定命名。

public class Namespace {
    public static final String USER_TOKEN_CACHE_NAMESPACE = "userToken";
}
(2)如果有一个UserTokenCache类,已经限定了范围是用户token缓存,这时候进行命名空间声明时,只需要使用namespace即可。

public class UserTokenCache {
    private final String namespace = "userToken";
}
回到这条规则,范围越大,说明对明明本身的限定就越少,所以我们在命名的时候需要加更多的限定语。反之,范围越小,我们就可以使用更小的限定语,作用域越小,产生歧义的可能性就越小。

2.9 防止名称误导
编写程序时,应该防止误导性的命名出现。误导性的命名不仅误导他人,也误导自己。
int[] userList = new int[MAXN];    //存储一组用户
如果用userList表示一个用户列表,但他并不是一个List,而是一个数组,那么这就是一个误导性的命名。对于List这种有特殊意义的词语,最好在只有该标识符真的是一个List时,才可以在命名中包含它。 

2.10 一致性
不管我们使用何种规约进行命名,在团队内部一定要达成一致,否则在系统中明明将非常混乱。

3 闯关要求
题目:题目给出了一个类,其中类名的命名不规范。请根据所学知识,将不符合命名规范的代码修改规范。

对应知识点: 类名使用大驼峰命名风格。

第2关:注释

1 任务描述
本关任务:本关考察学生对注释规范的掌握。学生首先学习相关知识,然后进行实践以加深对知识的掌握。

2 相关知识
注释是用自然语言对代码的解释和说明,其目的是提高代码的可读性,不会被计算机编译。很多人有一个误区,那就是注释越多越好,其实不然。注释产生的原因,本质是代码的可读性太差,我们应该遵守良好的编程规范和命名风格,努力提高代码的可读性,从而减少注释。
以下是注释可能会出现的几个问题:注释可能会将代码块分割,降低程序的可读性;注释不一定真实的反应了代码的意图,可能具有误导性;注释需要随着代码的更新而不断地更新,增加了维护成本。一旦注释过时,可能产生严重的后果。
接下来介绍一些添加注释的建议。
(1)不要做代码的重复。这样的注释并不能提供比代码更多的信息,反而会分散阅读者的精力,如以下注释。

 // Class representing a point.
class Point {
    private double X;
    private double Y;
    //Get the X value.
    public double getX(){
        return X;
    }
    //Get the Y value.
    public double getY(){
        return Y;
    }
}
(2)不要写形式主义的注释,比如刻板的遵循“每个变量都应该有说明”。如果变量有一个好的命名,那么就不需要毫无意义的注释。

 //The grade of the student
private double gradeOfStudent;
(3)尽量不要做代码的解释。如果一段代码复杂到必须用自然语言编写注释来解释,那么最好花时间重构它,而不是用注释来粉饰,好的代码本身就是注释。

(4)对代码的意图进行阐释。描述代码是用来做什么的而不是代码怎么做的,这是最有用的注释类型。

(5)确保注释如实反映代码的本意。如果某个函数的注释与函数的代码本意不符,那将会对函数调用者产生误导。注释必须随着代码的更新而不断地更新,注释版本落后于代码版本也会歪曲代码的本意。
(6)可以添加一些提供信息的注释。如在代码开头添加版权和著作权的声明、描述某个抽象方法的返回值。

3 闯关要求
题目:题目给出了一段包含注释代码,请根据所学知识,删除其中无用的代码。

对应知识点: 不要做代码的重复。这样的注释并不能提供比代码更多的信息,反而会分散阅读者的精力。

第3关:布局

1 任务描述
本关任务:本关考察学生对布局规范的掌握,包括竖直间隔、竖直顺序、空格和缩进等。学生首先学习相关知识,然后进行实践以加深对知识的掌握。

2 相关知识
程序有一个合理的布局,可以使程序的逻辑结构、语句的关系更加清晰,大大提高程序的可读性。以下将程序的布局分为竖直间隔、竖直顺序、空格、缩进来介绍。

2.1 竖直间隔
在阅读代码时,我们总是一次阅读一个代码组。代码组,即两个空行之间的内容。我们如果按照代码的逻辑,将代码分为一个个的代码组,那么可读性会大大提高。对包的封装、导入和函数的声明之间最好也用空行隔开。
如一下代码所示,合理运用空行,我们可以清晰的看出该类有两个属性和两个方法。

package animal;
import java.util.*;
public class Animal {
    private int age;
    private double weight;
    
    public Animal() {
        this.age = 0;
        this.weight = 0;
    }
    
    public Animal(int age, double weight) {
        this.age = age;
        this.weight = weight;
    }
}
即使对于一个简单的程序,如果去掉这些空行,程序的可读性降低了很多,无法做到一目了然。若是代码功能较为复杂且行数较多,合理运用空行是尤为重要的。

2.2 竖直顺序
对于一个程序,代码段在竖直方向上的排列顺序也会影响到程序的可读性。
在我们阅读一段代码时,总是希望先从总体上了解这段程序做了什么,若有需要,再有针对性地阅读代码实现的细节。所以我们应该将被调用的函数放在执行调用的函数后面,这样才符合从整体到局部的阅读顺序。

2.3 空格
在程序中使用空格,会起到分隔或者强调的作用,也可以提高程序的可读性。
我们可以在函数参数列表中的每一个逗号后面添加空格,或者在for循环的循环判断条件中,每个分号后面加一个空格,加强分隔效果。

for(int I = 0; I < length; ++i)
for(int i=0;i<length;++i)
观察以上两行代码,可以明显的发现第一行代码更美观,可读性更高。细心的读者可能会发现,第一行代码的运算符两边也都添加了空格,这也是空格的一个重要用法。空格可以在视觉上强调运算符,提高可读性。但也不是任何时候都要在运算符两侧添加空格,如以下代码。

num = a * b + c * d;
num = a*b + c*d;
一眼看去,可以发现第二行代码中,乘法运算符两侧不添加空格代码的可读性会更高,因为这样我们可以轻松的看到运算的优先级。

2.4 缩进
缩进指的是在代码与页面边缘增加一些空格来提高程序的可读性和可维护性。没有缩进的代码可读性是非常差的,尤其是代码中有多层条件或循环嵌套的时,如以下示例。

while (p1 <= mid || p2 <= right) {
if (p1 > mid) {
sorted[p++] = (int) sum[p2++];
} else if (p2 > right) {
sorted[p++] = (int) sum[p1++];
} else {
if (sum[p1] < sum[p2]) {
sorted[p++] = (int) sum[p1++];
} else {
sorted[p++] = (int) sum[p2++];
}
}
}
以下是一个正确缩进的示例。正确的代码缩进,可以使程序的逻辑结构更加清晰,提高可读性的同时也方便维护。

while (p1 <= mid || p2 <= right) {
     if (p1 > mid) {
            orted[p++] = (int) sum[p2++];
     } else if (p2 > right) {
         sorted[p++] = (int) sum[p1++];
     } else {
         if (sum[p1] < sum[p2]) {
                 sorted[p++] = (int) sum[p1++];
            } else {
                 sorted[p++] = (int) sum[p2++];
         }
     }
}
正如孟子在《离娄章句上》中所说,“没有规矩,不成方圆”。在个人或者团队编程中,我们应该遵守良好的布局规则,这样会大大提高编程效率,否则到头来将会是事倍功半。

3 闯关要求
题目:题目给出了一段代码,请合理的利用空行,提高其可读性。

对应知识点: 代码组,即两个空行之间的内容。我们如果按照代码的逻辑,将代码分为一个个的代码组,那么可读性会大大提高。对包的封装、导入和函数的声明之间最好也用空行隔开。

第4关:数据说明

1 任务描述
本关任务:本关考察学生对数据说明规范的掌握,包括数据说明的次序、说明语句中的变量顺序和复杂数据结构的注释说明。学生首先学习相关知识,然后进行实践以加深对知识的掌握。

2 相关知识
为了使源程序中的数据更容易理解和维护,我们应该建立良好的数据说明风格,以下是一些应该遵循的数据说明原则:

2.1 数据说明的次序规范化
为了使数据在调试、测试、维护时更容易查找,数据说明的次序应当规范化。例如可以按数据类型或数据结构来确定数据说明的顺序。

2.2 说明语句中的变量顺序安排合理
当多个变量同时在一个语句中说明时,若存在不同类型的变量,首先应该将这些变量分类,然后再按照字母顺序排列。
例如应该把

int size, length, width, cost, price;
写成

int cost, price;
int length, size, width;
2.3 使用注释来说明复杂数据结构
对于用户自己定义的复杂数据结构,应当增加注解来说明使用该程序设计语言实现这个数据结构的方法和特点。

3 闯关要求
题目:
假设我们的项目需要声明长方体的长(length)、宽(width)、高(height)、周长(perimeter)、表面积(area)、底面积(baseArea)、侧面积(lateralArea)和体积(volume)等变量。
请按照数据说明原则,修改说明语句中的变量顺序,将这些变量按单位类型分成多个语句,然后每个语句中的变量按照字母顺序排列。

对应知识点: 说明语句中的变量顺序安排合理,当多个变量同时在一个语句中说明时,若存在不同类型的变量,首先应该将这些变量分类,然后再按照字母顺序排列。

第5关:语句构造

1 任务描述
本关任务:本关考察学生对语句构造规范的掌握,学生首先学习相关知识,然后进行实践以加深对知识的掌握。

2 相关知识
语句的构造直接影响程序的可读性,应当遵循以下原则:

(1)不要为了节省空间而将多条语句写在同一行,并且当一条语句过长时,应该分为多行。

(2)尽可能通过重构代码来避免构造多层的循环嵌套和条件嵌套语句。

程序员有时为了使方法只有一个出口,常常会写出条件嵌套的代码。这样的代码掩盖了正常的代码逻辑,导致方法的正常执行路径不清晰,如以下代码。

 public double countPayment() {
    double result;
    if (this.isJuniorMember) {
        result = juniorMemberPayment();
    }
    else {
        if (this.isSeniorMember) {
            result = seniorMemberPayment();
        }
        else {
            if (this.isOrdinaryUser) {
                result = retiredPayment();
            }
            else {
                result = normalPayment();
            }
        }
    }
    return result;
}
接下来我们对以上代码进行重构,可以看到,消除了复杂条件嵌套语句后,代码的逻辑变得非常清晰。

 public double countPayment() {
    if (this.isJuniorMember) {
        return juniorMemberPayment();
    }
    if (this.isSeniorMember) {
        return seniorMemberPayment();
    }
    if (this.isOrdinaryUser) {
        return retiredPayment();
    }
    return normalPayment();
}
(3)不要为了显示编程技巧而降低程序的可读性,语句构造应当简介明了。

例如编写代码实现交换两个变量的值,最好使用代码片段2而不是代码片段1,这样可读性会很差,而且并不是每个读者都知道这段代码的含义。

 //代码片段1
a = a+b-(b=a);
//代码片段2
int tmp = a;
a = b;
b = tmp;
(4)对于复杂的算术表达式或逻辑表达式,多使用圆括号,这样可以使表达式的运算次序更加的直观。

下面举一个简单的例子:使用位运算求两个整数的平均值时,如下面代码

int mid = left + right >> 1;

我们知道“+”运算符的优先级比“>>”运算符高,所以上述代码是正确的,但为了使表达式一目了然,还是建议加上括号,代码如下:

int mid = (left + right) >> 1;

3 闯关要求
题目:
题目给出了一个语句,该语句将多条语句写在了同一行,可读性低。请对该语句进行重构,提高其可读性。

对应知识点: 不要为了节省空间而将多条语句写在同一行,并且当一条语句过长时,应该分为多行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值