[第二届构建之法论坛] 预培训文档(Java版)
本博客是第二届构建之法论坛暨软件工程培训活动预培训文档中【适用于结对编程部分的Java版本】,需要实验者有一部分Java基础。
目录
Part0.背景
阿超家里的孩子上小学一年级了,这个暑假老师给家长们布置了一个作业:家长每天要给孩子出一些合理的,但要有些难度的四则运算题目,并且家长要对孩子的作业打分记录。
作为程序员的阿超心想,既然每天都需要出题,那何不做一个可以自动生成小学四则运算题目与解决题目的命令行 “软件”呢。他把老师的话翻译一下,就形成了这个软件的需求:
- 程序接收一个命令行参数 n,然后随机产生 n 道加减乘除(分别使用符号+-*/来表示)练习题,每个数字在 0 和 100 之间,运算符在2个 到 3 个 之间。
- 由于阿超的孩子才上一年级,并不知道分数所以软件所出的练习题在运算过程中不得出现非整数,比如不能出现 3÷5+2=2.6 这样的算式。
- 练习题生成好后,将生成的n道练习题及其对应的正确答案输出到一个文件 subject.txt 中。
- 当程序接收的参数为4时,以下为一个输出文件示例。
13+17-1=29
11*15-5=160
3+10+4-16=1
15÷5+3-2=4
这次阿超选择使用他最拿手的 Java 语言来完成这样的需求,工欲善其事必先利其器,第一步就需要先安装一个好用的IDE,在这里我们推荐使用 Eclipse 或 IntelliJ IDEA。
Part1.配置环境
配置JDK
首先来区分一下2个最容易被混淆的概念 JDK 与 JRE。JDK全称为 Java Development Kit,Java程序的开发人员必须安装;JRE全称为Java Runtime Environment,它只是Java程序的一个运行环境。JDK中一般已经带了JRE包,所以我们只需要下载JDK并安装配置即可。在本次培训中,我们使用的是 Java SE Development Kit 8u172,各个平台的JDK下载链接如下:
如果上面的链接直接打开显示未授权,请先打开授权链接,并点击下图中红色框中的选项,再点击上面的网址即可。
Linux 平台
在Linux平台下,下载Java压缩包后,使用 cd 进入压缩包所在文件夹,使用命令行解压缩:
tar -xzvf jdk-8u172-linux-x64.tar.gz; mv jdk-8u121 ~/jdk
解压缩完成后,在环境变量中增加Java的主目录,增加环境变量主要是为了能在任何目录下搜索到Java二进制程序。
使用 vim 打开 .bashrc 文件,写入下面的内容
export JAVA_HOME=~/jdk
export PATH=$JAVA_HOME/bin:$PATH
注意在编辑完成后,使用 source ~/.bashrc
可以立刻加载修改后的设置。
Windows 平台
在windows平台下,下载的是Java JDK的安装程序。下载完成后,双击可执行文件,点击下一步。在选择安装路径时,可以选择默认(默认会安装到 C:\Program Files\Java\jdk1.8.0_172\),不过还是推荐点击【更改】,换一个更好记忆的路径。点击下一步,即可开始安装。
安装完JDK后,接下来要安装的是公共 JRE 环境,JRE路径推荐默认,但注意不要与上面JDK的安装路径完全一致。(JDK自带了一个JRE环境)
安装完成后,打开控制面板,点击系统,高级系统设置,设置环境变量。
配置 Path 变量,将刚才JDK的安装路径填入Path 变量中。比如我们把JDK安装在了 D:\java\jdk下,则点击Path变量,选择编辑。
在弹出的界面里选择【新建】,将 D:\java\jdk\bin 和 D:\java\jdk\jre\bin 填入即可。
下载安装IDE
本教程与培训期间所使用的标准IDE都是基于 Intellij IDEA 社区版,所以在 地址 下载社区版即可。我们这里限定使用 Version: 2018.1.2 及以上。
安装非常简单,在安装完成后第一次运行时,会让用户选择是否导入以前的设置,我们选择不导入即可。
在安装好IDE后,阿超没有仔细分析软件需求,就火急火燎地开始编写代码了。二十分钟后,四则运算生成器 v1.0 诞生了,伴随着在源代码管理平台上的唯一一次也是最后一次commit与push,阿超想当然地认为他的软件开发已经结束了。但是这个软件并不完善,下面让我们通过教程来完善这个项目吧。
Part2.克隆项目
阿超的项目放在了当下最流行的源代码管理平台Github上,仓库地址。那么,我们如何在阿超项目的基础上进行开发呢?
1.在 https://github.com/join 这个网址处申请注册一个Github账号,申请成功后可在https://github.com/login 处利用刚刚注册的账号进行登录,才能开始在Github上进行开发。
2.成功登录后,输入阿超仓库的网址,点击右上角的 Fork,将阿超的四则运算库拷贝到自己的同名仓库中,如下图所示:
3.拷贝成功后,可以看到自己已经拥有了一个同名仓库。这里我们登录的是 buaase 的账号:
4.在自己的电脑上安装 Git 软件,Git 的安装教程在这里。在自己拷贝项目的主页的绿色按钮处可以找到一个可克隆的项目地址,下面是一个示例:
5.在 我的电脑 中任意找一个目录,打开Git 命令行软件(Windows上可在空白处右键打开Git Bash),输入 git clone
6.完成上述操作后,可在当前目录下看到一个与仓库同名的文件夹【Calculator】,这就是克隆到本地的项目。注意,默认克隆的分支是 java。进入项目文件夹,新建一个文件夹,重命名为你的Github账号名。
7.以Github账号命名的文件夹作为项目目录,在 IntelliJ IDEA中新建一个工程。注意,如果是第一次新建工程,IDEA会要求用户配置Java SDK位置,此时只需要点击 Project SDK 右侧的 New... 按钮,选择开始配置好的 JDK 目录即可。还有,为方便后续的自动测试,在操作时不要导入任何 package。操作正确后,在工程的项目目录中应该会出现一个src文件夹,文件夹中有 Main.java 文件。接着,用克隆项目 src 文件夹下的 Main.java 替换默认的 Main.java文件。一个动态演示图如下所示:
8.现在这个Main.java文件应该可以正常运行。现在让我们来尝试运行一下右键单击 Edit Configuration,配置 class 的正确位置(利用默认的就好)。也可以点击菜单中的 【File】,进入【Project Structure】,查看默认的各种参数设置,包括 SDK(JDK设置,这里我们默认是 1.8.0),Language level(这一步会进行编译前的语法检查,我们这里选择 8 即可),最下方的则是编译产生 class 文件的存放位置。
配置好中间结果的产生文件夹后就可以运行Main.java了,选中【Main.java】,右键选择【Run 'Main'】,正常的现象是打印两行,第一行是一个四则运算式,第二行则是该四则运算式的计算结果。一个成功的运行结果示例如下(每次都会随机生成运算式,并不一定一样)
9.接下来接连使用 git add,git commit -m "Message"(Message是你要写的内容)即可利用 Git 记录下所有的改动。如果是初次使用 Git,请在使用上述两条命令前使用如下两条命令配置自己的个人邮箱与Commit时的用户名,这里的邮箱和用户名最好与 Github 账号保持一致。
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com
下面是一些常见的Git操作,可留作备忘
$ git clone [url]
下载一个项目以及它所有的版本历史
$ git add [file]
将文件进行快照处理用于版本控制
$ git commit -m"[descriptive message]"
将文件快照永久地记录在版本历史中
$ git push
上传当前本地分支commit到GitHub上
$ git pull
下载服务器上最新的本部并合并更改到本地
$ git reset [commit hash]
撤销所有[commit hash]后的的commit,在本地保存更改
$ git log
列出当前分支的版本历史
对于Github平台有疑问的,可以在 http://github.com/help 找到解决方案。
Part3.单元测试
在软件工程里多人合作是无法避免的,这样就会出现一个人写的模块被另一个人写的模块调用的情况。很多误解、疏忽都容易发生在两个模块之间。如何能让自己写的模块尽量无懈可击?单元测试就是一个很有效的解决方案。在实现上,模块是否满足需求和设计预期的效果很重要,单元测试正是验证代码行为是否满足预期的有效手段之一。同样地,我们现在希望知道阿超的代码中存在什么样的问题,也需要先对阿超的模块进行单元测试。
在Java中我们常用 Junit4 来进行单元测试,它是Java中一套强大的测试框架,IntelliJ IDEA 对它也有非常好的支持。
首先要为IDEA中安装JUnit插件,选择File->Setting打开设置对话框。设置在对话框中选择Plugins, 单击Install JetBrains plugin...按钮打开插件安装界面:
Junit 已经在 IDEA 中内置了,下面我们安装一个可以帮助我们自动生成测试文件的框架 Generate Tests。在插件安装对话框的搜索中输入junit,即可找到它, 单击右侧绿色的Install按钮即可安装。
现在看向我们新建项目中的 Main.java 文件,可以看到有一个名叫 Solve 的方法。
点击 Solve 方法名,单击类名,左侧会出现一个灯泡状图标,单击图标或按Alt + Entert,在弹出的菜单中选择Create Test(或者可以右键点击 GoTo -> Test,Create Test):
注意这里可能会出现【JUnit4 library not found in the module】,此时只需要点击 Fix 引入 JUnit 测试框架即可。注意我们这里使用的单元测试框架是 JUnit4,记得在最下方要勾选上进行测试的方法。完成后,应该会默认在 src 目录下生成一个名叫 MainTest.java 的文件,正确生成框架的情形如下所示:
注意,如果自动生成的测试代码无法通过编译(在IDE中表现为 import org.junit.Assert. 导入失败),点击菜单栏 File -> Project Structure,在 Modules 的 Dependencies 中找到 Junit4,确保其 Scope 为 Compile*。
通过阅读方法 Solve 的注释与代码,我们不难发现,它的参数是字符串形式的四则运算表达式,返回的应该是该表达式的答案。现在我们来填充第一个测试用例,注意每个测试用例的名字没有限制,但其一定要在方法体前加上注解 @Test 作为标志。注意到题目中要求题目范围在1到100间,我们先来写一个最简单的测试样例吧。
import org.junit.Assert;
import org.junit.Test;
public class MainTest {
@Test
public void solve() {
String sum = Main.Solve("11+22");
Assert.assertEquals("11+22=33", sum);
}
}
这里我们用到了 Assert(断言)。编写单元测试时,我们总是会做出一些假设,比如我们期望一个函数在接受预期的输入后就返回预期的输出,断言就是用于在代码中捕捉这些假设。一般来说,单元测试中都会有断言的存在,没有断言存在的单元测试其实是“假大空”的,没有任何对程序输入输出的假设约束。
下面我们来运行一下这个单元测试,看 Solve 函数是否符合我们的期望。右键单击 MainTest.java 文件,运行该测试文件即可,如下图所示
在单元测试运行完毕后,IntelliJ IDEA的下方会弹出一个测试结果窗口。绿色代表通过,黄色代表失败。从本次的结果来看,我们通过了这个单元测试。
那么也就是说,当 solve 函数的输入为 "11+22” 时,其实际的输出就是 "11+22=33",与预期现象吻合。当然,我们这里只是通过了一个简单的测试用例,不能说这个函数就是一定正确的。所以我们需要加一些单元测试,以验证在怎样的情况下这个函数可能会出错,现在请你帮助阿超补充一些针对 solve 方法的单元测试用例吧。
Part4.基本操作
上面我们学习了如何使用 IDE 进行单元测试,也测试出了 solve 方法的确有些问题。那么该如何定位问题所在呢?这就要用到 IDE的调试功能了。下面我们就来介绍一下 IntelliJ IDEA的调试方法。
断点
调试程序首先要会设置断点和单步运行。设置断点比较简单,在要设置断点的行号旁用鼠标单击一下就行了(或使用Ctrl+F8快捷键),如下图所示,我们在第 34 行设了个断点:
单步运行
在设置好断点后,我们启动Debug模式。右键单击 Main.java 文件,选择 Deug 'Main.main()' ,这时可以看到程序已经运行到刚刚打的断点处前。下方的Debug窗口可以看到各个变量的值。
此时我们的第34行代码并没有执行,下面我们利用单步运行的方法执行该语句。单步运行有两种:Step Into(快捷捷F7) 和 Step Over(快捷捷F8),这两种单步运行功能在运行语句时没有区别,在执行方法调用语句时,Step Into会跳入方法实现,Step Over会直接执行完方法,实际使用中我们优先使用Step Over,只有方法执行出错了,说明程序问题在被调用方法中,这时再回来通过Step Into进入方法进行调试。我们单击一下Step Over图标(或F8),程序停在了第35行,这时查看变量i的值,会看到formulaChar的值。
条件断点
有些情况下我们只希望断点在某些条件下才成立,该如何做呢?其实非常简单,右键单击红色的断点符号,即可弹出条件选项。
在这里我们可以输入 Condition,设定为只有某些前置变量的值满足条件时我们才会触发断点,帮助我们高效率测试。比如我们这里设定 Condition 为 j == 0:
从图中我们也可以看到,此时 j 为 0。
我们现在的程序中暗藏了两个BUG,一个是不符合题目的需求,另一个是实现上有一个小问题。现在请你利用刚学习到的单元测试与Debug的相关知识,找出程序的bug吧!
Part5.回归测试
单元测试不仅仅用来保证当前代码的正确性,更重要的是用来保证代码修复、改进或重构之后的正确性。也就是说,在每次修改完bug之后,我们其实都需要运行一遍来看看是不是满足之前所有的单元测试样例。所以,在每次因为现有的 failed test 而修复原有代码后,最好都全部运行一遍单元测试,保证以前 passed test 仍然是可以通过的。
同样地,Git 的使用也是讲究勤提交,提交的粒度最好是细到每个小功能的完成。一个小功能可以是一处小bug的修复,也可以是一个简单函数的实现。所以,在我们本次的编程训练任务中,Git 至少会提交 2 次或以上。
Part6.效能工具介绍
为了测试并改进程序生成四则运算算式的效率,我们需要使用效能分析工具。效能分析工具并不能帮助我们直接改进算法的效率,但它可以帮我们分析找到代码中执行效率最差,也就是所谓【效能瓶颈】的部分。这之后我们就可以把精力花费在改进瓶颈上,从而高效快速地提升程序性能。
JProfiler 是一款大名鼎鼎的 Java 效能分析工具,为了使用它,首先我们需要下载一个JProfiler的可执行文件。在这里我们推荐 10.1.1 版本,下载地址在 https://www.ej-technologies.com/download/jprofiler/files,选择适合电脑版本的 JProfiler 文件即可。下面以 Windows 用户的安装过程为例。
首先进入官网后,点击右侧 Download 按钮下载 JProfiler 的安装程序:
下载完成后,打开 exe 文件,会让用户指定安装路径。(默认安装路径是C:\Program Files\jprofiler10),推荐自定义安装方式,放在一个比较容易寻找的路径(路径中最好不要含有中文)
然后一路 next,点击 Install 即可成功安装。在安装成功后会提示用户与IDE进行集成,这里不管它,直接 Next 即可。
接下来 JProfiler 会自动启动,值得注意的是,JProfiler 是商业软件,如果想使用需要购买 License。为了详细介绍使其使用过程,这里使用 10天 试用版本。
接下来我们将JProfiler 集成到 Intellij IDEA 中。我们可以方便地通过设置 IntelliJ IDEA 的插件将其集成进IDE中。首先选择 Setting -> Plugins ,输入 JProfiler,如果界面中显示 No plugins found. 那么点击提示语右侧的 Search in repositions 即可找到 JProfiler。之后点击右侧的绿色按钮 Install,重新启动 IDE。
成功安装后,在IDE的菜单栏 Run 中可以看到一个 Profile 的按钮,如下图所示:
在首次使用 Profile 时我们需要选择 jprofiler.exe 所在的位置,这时定位到刚才安装 JProfiler 的目录,找到 bin 文件夹即可找到 jrpofiler.exe,点击 【OK】即可开始性能分析。
在设置分析目标时,因为我们目前只关注CPU的使用情况,所以选择 Sampling 即可。
先别急着开始进行性能分析。我们的代码目前只产生1个四则运算算式,不存在性能问题。我们首先来给代码多加几百万个循环,让它运行【足够长】时间,才能准确测出代码的效能问题。增加循环体后的main 函数体如下所示,这里为了避免输出对分析灵敏度的影响,我们把输出去掉了。
public static void main(String[] args) {
for(int i = 0;i < 10000000; i++) {
String question = MakeFormula();
String ret = Solve(question);
}
}
右键点击 Main.java 文件,在弹出的菜单栏中选择【Profile 'Main' 】即可开始进行性能分析。弹出的设置中推荐手工更改几个设置,一个是 Initial recording profile 设置为 【CPU recording】,将 JVM exit action 设置为 【Save and imeediately open a snapshot】,然后点击【OK】即可开始性能分析。
当前选项设置了在运行一段时间后自动保存并打开一个Profile 快照,运行结束后即可看到性能分析的结果。在快照左侧选择 【CPU View】,点击到 【Call Tree】,可以在右侧看到每个函数占用的CPU时间。
详细的方法消耗时间如下所示。从消耗时间中我们可以看出 Main.Solve 方法占用的时间最多,占到了 87%,如果想优化程序性能,接下来应当着手于该方法。
Part7.提交代码
在完成 Debug 与 单元测试之后,我们现在来学习一下如何提交代码到 Github 上,并利用 Github 进行团队协作。之前我们已经介绍过了 git add 与 git commit 命令,但这两条命令只会对本地的仓库进行修改,也就是说之前的所有操作都是离线的。我们要想让 Github 上也跟踪到最新的改变,就需要使用 git push
命令。
在使用该命令前,请确保所有本地的改动都已经 add 并 commit 了。可以用 git status 来检查:
出现如图所示的【nothing to commit】即说明已经可以 push 了。使用 push 命令后,会弹出一个窗口要求登录 Github,此时输入Github的用户名或邮箱 与 密码 即可成功 push。
成功的提示如下所示,其中 master 部分应该是 java / cplusplus。
在完成 push 后,我们就可以开始向源仓库(即阿超的仓库)发起Pull Request(简称PR,指发起请求给仓库贡献代码)。打开你 Fork 后的项目主页,如图所示,点击按钮【New pull request】
如果你按照教程的节奏一步步走下来了,那么在点击 New pull request后,应该出现如下所示界面。如果不是,请联系群内的助教/老师,或者在本博客下留言补充错误截图。
此时点击【Create pull request】即可发起请求。等待仓库主人阿超通过审核后,你的代码就可以成功合并进阿超的仓库。至此就完成了整个教程,恭喜!
参考链接
- JUNIT 单元测试:https://www.ibm.com/developerworks/cn/java/j-lo-junit4/index.html
- IntelliJ IDEA 简易教程:http://www.cnblogs.com/rocedu/p/6371315.html#SECUNITTEST
- JProfiler 与 IntelliJ IDEA的集成:http://www.cnblogs.com/zhangyaxiao/p/6678385.html