结对第二次—文献摘要热词统计及进阶需求

作业描述

所属课程软件工程1916|W(福州大学)
作业要求结对第二次—文献摘要热词统计及进阶需求
结对学号221600327221600329
基本需求Github项目地址PairProject1-Java
进阶需求Github项目地址PairProject2-Java
作业目标运用结对编程完成作业,增强团队协作能力
参考文献《构建之法》、《数据结构与算法分析 Java语言描述》

PSP表格

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划2030
• Estimate• 估计这个任务需要多少时间2030
Development开发10601290
• Analysis• 需求分析 (包括学习新技术)60120
• Design Spec• 生成设计文档1020
• Design Review• 设计复审3020
• Coding Standard• 代码规范 (为目前的开发制定合适的规范)1010
• Design• 具体设计3040
• Coding• 具体编码800960
• Code Review• 代码复审6060
• Test• 测试(自我测试,修改代码,提交修改)6060
Reporting报告80120
• Test Report• 测试报告4060
• Size Measurement• 计算工作量1020
• Postmortem & Process Improvement Plan• 事后总结, 并提出过程改进计划3040
合计11601440

一、WordCount基本需求

1.1、PSP表格

1.2、需求分析

(一)成品:wordCount的命令行程序
(二)功能性需求:统计input.txt中的以下几个指标
  1. 统计文件的字符数
    • 只需要统计Ascii码,汉字不需考虑
    • 空格,水平制表符,换行符,均算字符
  2. 统计文件的单词总数,单词:至少以4个英文字母开头,跟上字母数字符号,单词以分隔符分割,不区分大小写
    • 英文字母: A-Z,a-z
    • 字母数字符号:A-Z, a-z,0-9
    • 分割符:空格,非字母数字符号
    • 例:file123是一个单词,123file不是一个单词。file,File和FILE是同一个单词
  3. 统计文件的有效行数:任何包含非空白字符的行,都需要统计。
  4. 统计文件中各单词的出现次数,最终只输出频率最高的10个。频率相同的单词,优先输出字典序靠前的单词。
  5. 按照字典序输出到文件result.txt:例如,windows95,windows98和windows2000同时出现时,则先输出windows2000
    • 输出的单词统一为小写格式
  6. 输出的格式为

characters: number
words: number
lines: number
: number
: number
...

(三)非功能性需求

1.接口封装

  • 统计字符数 ;

  • 统计单词数 ;
  • 统计最多的10个单词及其词频 ;

1.3、解题思路

1.3.1统计字符数:

我理解的input.txt的内容应该是,文件中只包含ascii码以及空格,水平制表符,换行符这些字符。所以想直接用String存储文件内容,用String.length()来得到字符数。但是这个会有一个问题,就是在windows平台下,换行符是用"\r\n",String.length()会统计为两个字符,用“\n”代替“\r\n”,即可解决问题。

1.3.2统计单词数:

文件由两种字符分割符(空格,非字母数字符号)和非分隔符构成,单词统计与非分割符无关,所以直接为了接下来split方便,将所有的分割符都替换为“|”,第二步分割字符串,第三步,使用正则表达式匹配题目所规定的单词,统计单词数。

1.3.3统计有效行数:

一开始我想的统计行数是使用readline();去读出每一行,然后逐行判断这行有没有ascii码值<32的字符存在,因为ascii码值小于32的是在文本中不显示的,所以我开始的想法就是这样的;不过试了一下之后发现对于"\r\n"之类的字符和空白行单靠ascii码是区分不出来的,然后在网上找了资料之后,发现String.trim().isEmpty()是可以去除空白行然后统计的,最后使用的是这个方法。

1.3.4统计各单词的出现次数,输出频率Top10的单词:

因为处理场景为单机,所以决定选用散列表,顺序扫描刚刚的到的单词数组,当扫描到某个关键词时,就去散列表里查询。如果存在,就将对应的次数加一;如果不存在,就将他插入到散列表,并记录为1,以此类推,遍历完后,散列表中就存储了不重复的单词以及其出现的次数。
然后就是求Top10,建立一个大小为10的小顶堆,遍历散列表,依次取出每个单词及对应出现的次数,然后与堆顶的单词比较。如果出现次数比堆顶单词的次数多,就删除堆顶单词,将这个次数更多的关键词加入到堆中,以此类推,遍历完散列表,堆中的单词就是出现次数top10的单词。然后排序输出。

1.4、设计过程

1.4.1代码组织&接口设计:
  • bean包:Word类

  • unitl包:BasicWordCount类、IOUnitls类
    • BasicWordCount类:

      • 统计字符数函数 :

        /**
        * 计算文件字符数
        * @param fileName 文件名
        * @return long 字符数
        */
        public long characterCount(String fileName){}
      • 统计行数 :

        /**
        * 计算文件行数
        * @param fileName 文件名
        * @return long 行数
        */
        public long lineCount(String fileName){}
      • 统计单词数 :

        /**
        * 计算文件单词数
        * @param fileName 文件名
        * @return long 单词数
        */
        public long wordCount(String fileName){}
      • 统计词频top10的10个单词及其词频 :

        /**
        * 计算出现次数top10的单词及其词频
        * @param fileName 文件名
        * @return Word[] 单词数组
        */
        public Word[] topTenWord(String fileName) {}
    • IOUnitls类:

      • 读文件

        /**
        * 读入指定文件名的文件数据
        * @param fileName 文件名
        * @return BufferedReader
        * @throws IOException 
        */
        public static BufferedReader readFile(String fileName){}
      • 写文件

        /**
           * 将制定字符串输出到result.txt
           * @param fileContent 字符串
           */
          public static void writeFile(String fileContent, String fileName) {}
1.4.2关键代码及其流程图(词频Top10单词统计):

解题思路
流程图:
1592082-20190312162030355-1869944226.png
代码:

//进行统计的散列表
public Word[] topTenWord(String fileName) {
    ...//省略读取文件、将文本处理成单词数组的过程
        Map<String, Integer> countMap = new HashMap<>();
        for(int i = 0; i < splitStrings.length; i++) {
            if(Pattern.matches(regex, splitStrings[i])) {
                Integer outValue = countMap.get(splitStrings[i]);
                if (null == outValue) {
                      outValue = 0;
                }
                outValue++;
                countMap.put(splitStrings[i], outValue);
            }
        }
        //求top10
        PriorityQueue<Word> topN = new PriorityQueue<>(10, comp);
        Iterator<Map.Entry<String, Integer>> iter = countMap.entrySet().iterator();
        Map.Entry<String, Integer> entry;
        while (iter.hasNext()) {
          entry = iter.next();
          if (topN.size() < 10) {
            topN.offer(new Word(entry.getKey(), entry.getValue()));
          } else {
            // 如果当前数据比小顶堆的队头大,则加入,否则丢弃
            if (topN.peek().getCountNum() < entry.getValue()) {
              topN.poll();
              topN.offer(new Word(entry.getKey(), entry.getValue()));
            }
          }
        }
        //结果集
        Word[] result = null;
        int wordCount = countMap.size();
        if(wordCount < 10) {
            result = new Word[(int) wordCount];
        }else {
            result = new Word[10];
        }
        topN.toArray(result);
        //对top10单词排序
        Arrays.sort(result, comp);
        return result;
}

复杂度分析:遍历散列表需要 O(n) 的时间复杂度,一次堆化操作需要 O(logK) 的时间复杂度,所以最坏情况下,n个元素都入堆一次,所以最坏情况下,求TopK的时间复杂度是O(nlogk)。

1.5、性能分析

从CPU Call Tree图可以看出,耗时主要是在对字符串的分割和单词的正则匹配。

1592082-20190312232106112-109742329.png

1592082-20190312232102063-873420074.png

二、WordCount进阶需求

2.1、PSP表格

2.2、需求分析

2.2.1 成品:

①程序一:爬取CVPR2018论文到本地result.txt的命令行程序

②程序二:支持命令行参数的wordCount命令行程序

2.2.2功能性需求

(1)使用工具爬取论文信息**输出到result.txt文件
(2)自定义输入输出文件**
        WordCount.exe -i [file] -o [file]
(3)加入权重的词频统计**
         WordCount.exe -w [0|1]
(4)新增词组词频统计功能**
        WordCount.exe -m [number]
(5)自定义词频统计输出**
        WordCount.exe -n [number]
(6)多参数的混合使用**
        WordCount.exe -i input.txt -m 3 -n 3 -w 1 -o output.txt

2.3、解题思路

2.3.1 爬取CVPR论文数据

要从网页上爬取数据,就得分析网页HTML源代码构成,从CVPR2018首页的HTML代码可以看出,在首页的论文列表中,每一篇的论文标题在HTML代码上体现为如下形式:

<dt class="ptitle"><a href="xxx.html">论文标题</a></dt>

其中<a></a>标签里的href属性为论文详情页的网页地址。

由此我们可以获取每篇论文详情页的url

第一步就是在网页的DOM树中提取所有的<dt class="ptitle">的节点。
第二步从每个<dt class="ptitle">的节点中提取出<a>标签的href属性。

接着分析论文详情页的HTML代码,

我们可以发现,在详情页源代码中,论文标题的html代码长这个样子:

<div id="papertitle">论文标题</div>

论文摘要的html代码长这个样子:

<div id="abstract">这是论文摘要</div>

由此我们可以获取每篇论文的标题和摘要

第一步从网页的DOM树中找到<div id="papertitle">的节点,获取他的文本即为paper title。
第二步从网页的DOM树中找到<div id="abstract">的节点,获取他的文本即为paper abstract。
2.3.2 自定义参数

自定义参数的实现是先设计一个CommandLine类,把作业中的参数作为类的私有成员,然后从String []args中读取命令行参数,存进CommandLine类中,再使用getxxxx()公有函数取出相应的参数就可以使用自定义参数了。

2.3.3 加入权重的词频统计:

从爬取的论文数据可知,title独占一行,abstract独占一行,所以可以按行处理数据。如果-w 参数为1,修改基本需求的文件,当一行的开头是title:,那么这一行的单词的权重为10。其他的一律权重为1。

2.3.4 词组词频统计:

因为词组不能跨越Title、Abstract,而且需求给定Title和Abstract都是独占一行的,所以按行读取。

然后构造一个队列,对读取的一行开始扫描,接下来的判断逻辑如下图:

1592082-20190315172130233-1666581113.png

扫描完后,对获得的散列表进行topK操作,即可。

2.3.4 自定义词频统计

这个很简单,基本需求规定的是top10,而这里我们只要从参数获取topK的值,传入参数,遍历单词列表,然后维护一个大小为K的小顶堆即可。

2.4、设计过程

2.4.1 代码组织和接口设计

  • bean包:Word类、CommandLine类

  • unitl包:AdvancedWordCount类、Command类、IOUnitls类
    • AdvancedWordCount类:

      • 统计字符数函数 : 同基本需求
      • 统计行数 : 同基本需求
      • 统计单词数 : 同基本需求
      • 统计单词词频topK的K个单词及其词频 :

        /**
        * 计算单词的(加权)词频和topK
        * @param fileName 文件名
        * @param topK 词频前K的单词
        * @param isWeight 是否启用加权计算
        * @return Word[] 有序单词数组
        */
        public Word[] topKWordWeighting (String fileName, int topK, boolean isWeight) {}
      • 统计词组词频topK的K个词组及其词频 :
      /**
      * 计算词组的(加权)词频和topK
      * @param fileName 文件名
      * @param groupNum 几个单词为一个组
      * @param topK 词频前K的词组
      * @param isWeight 是否启用加权计算
      * @return Word[] 有序单词数组
      */
      public Word[] topKWordsWeighting (String fileName, int groupNum,int topK, boolean isWeight) {}
    • Command类:
      •  /**
         * 解析命令行参数
         * @param args 参数
         * @return CommandLine对象
         */
         public CommandLine parseCommand(String[] args) {}
    • IOUnitls类(同基本需求)

2.4.2 关键代码(词组词频统计之词组划分):解题思路

...
            //进行统计的散列表
            Map<String, Integer> countMap = new HashMap<>();
            while((line = bufferedReader.readLine()) != null) {
                String regexTile = "Title: .*";
                int weight = 1;
                if(Pattern.matches(regexTile, line)) {
                    line = line.replaceAll("Title: ", "");
                    weight = 10;
                }else {
                    line = line.replaceAll("Abstract: ", "");
                    weight = 1;
                }
                line = line.toLowerCase();
                //按字符分析
                int groupnumber = 0;//词组单词个数
                int wordStratPosition = 0;//单词开始下标
                int wordEndPosition = 0;//单词开始下标
                int flagPosition = 0;//标记词组第一个单词的结束位置
                Queue<String> wordReadyQueue = new LinkedList<String>();
                for(int i = 0; i < line.length()-3;) {
                    boolean flag = false;
                    
                    if(Character.isLetter(line.charAt(i))) {
                        if(Character.isLetter(line.charAt(i+1))) {
                            if(Character.isLetter(line.charAt(i+2))) {
                                if(Character.isLetter(line.charAt(i+3))) {
                                    wordStratPosition = i;
                                    for(int j=i+4; j < line.length(); ++j) {
                                        if(!Character.isLetterOrDigit(line.charAt(j))) {
                                            wordEndPosition = j;
                                            //得到一个单词,加入到队列
                                            wordReadyQueue.add(line.substring(wordStratPosition, wordEndPosition));                                     
                                            groupnumber++;//词组单词数+1
                                            if(groupnumber == 1) {
                                                flagPosition = j;
                                            }
                                            i=j;
                                            flag = true;
                                            break;
                                        }else if((j+1)==line.length()) {
                                            wordEndPosition = j+1;
                                            //得到一个单词,加入到队列
                                            wordReadyQueue.add(line.substring(wordStratPosition));
                                            groupnumber++;//词组单词数+1
                                            if(groupnumber == 1) {
                                                flagPosition = j;
                                            }
                                            i=j;
                                            flag = true;
                                            break;
                                        }
                                    }
                                }else {
                                    if(!Character.isLetterOrDigit(line.charAt(i))) {
                                        wordReadyQueue.add(line.charAt(i)+"");
                                    }else {
                                        while(wordReadyQueue.poll()!=null) {}//清空队列

                                        while(Character.isLetterOrDigit(line.charAt(i++))) {
                                            if(i >= line.length()){
                                                break;
                                            }
                                        }
                                        groupnumber = 0;
                                        flag = true;
                                    }
                                }
                            }else {
                                if(!Character.isLetterOrDigit(line.charAt(i))) {
                                    wordReadyQueue.add(line.charAt(i)+"");
                                }else {
                                    while(wordReadyQueue.poll()!=null) {}//清空队列

                                    while(Character.isLetterOrDigit(line.charAt(i++))) {
                                        if(i >= line.length()){
                                            break;
                                        }
                                    }
                                    groupnumber = 0;
                                    flag = true;
                                }
                            }
                        }else {
                            if(!Character.isLetterOrDigit(line.charAt(i))) {
                                wordReadyQueue.add(line.charAt(i)+"");
                            }else {
                                while(wordReadyQueue.poll()!=null) {}//清空队列

                                while(Character.isLetterOrDigit(line.charAt(i++))) {
                                    if(i >= line.length()){
                                        break;
                                    }
                                }
                                groupnumber = 0;
                                flag = true;
                            }
    
                        }
                    }else {
                        if(!Character.isLetterOrDigit(line.charAt(i))) {
                            if(groupnumber!=0) {
                                wordReadyQueue.add(line.charAt(i)+"");
                            }
                        }else {
                            while(wordReadyQueue.poll()!=null) {}//清空队列
                            while(Character.isLetterOrDigit(line.charAt(i++))) {
                                if(i >= line.length()){
                                    break;
                                }
                            }
                            groupnumber = 0;
                            flag = true;
                        }
                    }
                    if(groupnumber == groupNum) {//达到要求个数,加入散列表
                        String wordGroup = "";
                        String wordOfQueue = wordReadyQueue.poll();
                        while(wordOfQueue != null) {
                            wordGroup = wordGroup + wordOfQueue;
                            wordOfQueue = wordReadyQueue.poll();
                        }
                        groupnumber = 0;
                        //加权
                        Integer outValue = countMap.get(wordGroup.toString());
                        if (null == outValue) {
                              outValue = 0;
                        }
                        if(isWeight) {
                            outValue += weight;
                        }else {
                            outValue++;
                        }
                        countMap.put(wordGroup.toString(), outValue);       
                        i = flagPosition;
                    }
                    if(!flag) {
                        ++i;
                    }
                }
            }
            ...

2.4.3 关键代码(爬取论文标题和摘要):解题思路

因为之前写安卓应用曾经用过jsoup抓取教务系统上的成绩等数据,这次自然而然使用了jsoup来对论文进行爬取。代码如下:

        /**
         * 从cvpr网站爬取论文数据,并写入result.txt
         * @param URL
         */
        public static void getFile(String URL) {
            BufferedWriter bufferedWriter = null;
            try {
                File outputFile = new File("result.txt");
                bufferedWriter = new BufferedWriter(new FileWriter(outputFile));
                //get方式得到HTML数据
                //默认设置下,jsoup超时时间为3秒,鉴于当前网络环境,修改为10秒
                //默认设置下,jsoup最大获取的长度只有1024K,设置maxBodySize(0),可不限长度
                Document doc = Jsoup.connect(URL).timeout(10000).maxBodySize(0).get();
                //从HTML中选择所有class=ptitle的节点
                Elements paperList = doc.select("[class=ptitle]");
                //从ptitle节点中选择a标签的href属性值
                Elements links = paperList.select("a[href]");
                int count = 0;
                //分别当问每篇论文的详情页
                for (Element link : links) {
                    //论文详情页URL
                    String url = link.attr("abs:href");
                    Document paperDoc = Jsoup.connect(url).timeout(10000).maxBodySize(0).get();
                    //获取论文title
                    Elements paperTitle = paperDoc.select("[id=papertitle]");
                    String title = paperTitle.text();
                    //获取论文Abstract
                    Elements paperAbstract = paperDoc.select("[id=abstract]");
                    String abstracts = paperAbstract.text();
                    //数据写入文件
                    bufferedWriter.write(count++ + "\r\n");
                    bufferedWriter.write("Title: " + title + "\r\n");
                    bufferedWriter.write("Abstract: " + abstracts + "\r\n\r\n\r\n");
                }
            } catch (Exception e) {
                System.out.println("获取论文数据失败");
                e.printStackTrace();
            }finally {
                try {
                if(bufferedWriter != null) {
                    bufferedWriter.close();
                }
                }catch (Exception e) {
                    System.out.println("获取论文数据失败");
                    e.printStackTrace();
                }
            }
        }

2.5、性能分析及改进

2.5.1、性能分析

考虑到进阶的数据量可能比较大,在压力测试时,我们用了近60M的txt(近6000万的字符)文件进行测试。内存占用在1G左右,由于复用基本需求的接口,在性能测试时,依然是对字符串的分割(split)耗时最多占用内存很大,原因是因为原来的代码是把这6000万字符存放到字符数组里,在进行合法单词的判断。

1592082-20190315172058878-526769352.png
1592082-20190315172103319-147965803.png

2.5.2、改进方法

在统计单词时,直接遍历一遍文本数据,识别单词的start和end的下标,直接截取单词进行识别。代码如下:

    for(int i = 0; i<updateString.length();) {
        startPosition = i;
        endPosition = i;
        while(Character.isLetterOrDigit(updateString.charAt(i++))) {
            endPosition++;
        }
        if(Pattern.matches(regex, updateString.substring(startPosition, endPosition))) {
            countOfWord++;
        }
    }

2.6、单元测试

我们用的是Eclipse中的JUnit4进行的测试,我们总共设计了16个测试单元,其中字符计数,词计数,行计数各三个,进阶单词和词频各两个。测试用例都是根据作业要求设计的各种字符混杂,有空白行,数字字母混杂的形式进行测试,测试结果都显示我们的程序满足了题目的要求。

单元测试测试覆盖测试代码块测试个数
BasicWordCountTest.testCharacterCount()普通字符、空格、各种符号BasicWordCountTest.CharacterCount()3
BasicWordCountTest.testWordCount()普通字符、空格、各种符号,字母大小写,数字与字母各种组合BasicWordCountTest.WordCount()3
BasicWordCountTest.testLineCount()空白行、非空白行BasicWordCountTest.LineCount()3
BasicWordCountTest.testTopTenWord()普通字符、空格、各种符号,字母大小写,数字与字母各种组合BasicWordCountTest.TopTenWord()3
AdvancedWord.TesttopWordWeighting()混合单词AdvancedWord.topWordWeighting()2
AdvancedWord.TesttopWordsWeighting()混合词组AdvancedWord.topWordsWeighting()2

部分测试代码

package Untils;

import static org.junit.Assert.*;

import org.junit.Test;

public class BasicWordCountTest {
    public static BasicWordCount basic=new BasicWordCount();
    static String fileName="testinput2.txt";
    String []testTopWord= {"abcd123","here","your","aaaa","abss","bbbb","cccc","ddda","dera","esds"};
    int []testTopWordCount= {7,2,2,1,1,1,1,1,1,1};
    @Test
    //测试字符统计
    public void testCharacterCount() {
        System.out.println(basic.characterCount(fileName));
        assertEquals(189,basic.characterCount(fileName));
    }
    //测试单词统计
    @Test
    public void testWordCount() {
        basic.wordCount(fileName);
        assertEquals(21,basic.wordCount(fileName));
    }

    @Test
    //测试top10单词
    public void testTopTenWord() {
        basic.topTenWord(fileName);
        for(int i=9;i>0;i--) {
            System.out.println(testTopWord[9-i]+"=="+i+"=="+basic.topTenWord(fileName)[i].getKey());
            assertEquals(testTopWord[9-i],basic.topTenWord(fileName)[i].getKey());
            assertEquals(testTopWordCount[9-i],basic.topTenWord(fileName)[i].getCountNum());
        }
    }

    @Test
    //测试行数
    public void testLineCount() {
        basic.lineCount(fileName);
        assertEquals(4,basic.lineCount(fileName));
    }
}

其中一个测试结果截图
1592082-20190315171937008-985141512.png
1592082-20190315171944205-1816805567.png

在JUnit4的测试下,我们的总体代码覆盖率在80%左右,其中Main.java类是用来输出结果的类,所以没有加入测试,还有一些原因是很多的异常处理是没有办法触发的,这些代码没有覆盖住。不过我们主要代码的覆盖率都在90%左右,甚至以上的,所以说我们的测试结果还是对程序正确性做了有力验证的。

三、其他

3.1、代码规范:

​ 码出高效 :阿里巴巴Java开发手册终极版v1.3.0

3.2、分工情况

221600327:

​ 行数统计、命令行参数解析、输入输出模块代码编写、单元测试、文档编写

221600329:

​ 字符统计、单词统计、词频统计模块代码编写、爬虫设计和实现、性能测试、文档编写

3.3、Github的代码签入记录

1592082-20190315202451561-362950940.png

1592082-20190315202458328-408690591.png

3.4、遇到的代码模块异常或结对困难及解决方法

本来我们认为的词组词频统计会过滤掉单词间的非法字母数字字符,而后来助教重申了需求,要求输出这些字符。因为原先的做法是先把非法字母数字字符都替换掉,然后再进行操作。这需求一改,不就全凉了。后面只能一个字符一个字符的遍历,再截取单词。但是最后发现在数据量大的时候,不采用split去分割字符串,直接遍历反而速度更快,而且更省内存。

3.5、评价队友

3.5.1 221600329 评价 221600327

队友给够按时完成分配的任务,有耐心,可能基础不是特别扎实,但是肯学肯做,这点事值得肯定的。

需要改进的地方:编码能力需要加强。

3.5.2 221600327评价221600329

我的队友在我们结对中大部分时间是驾驶员的角色,他对于需求的分析能很快划分成若干个字模块,在编程方面他数据结构和算法方面的基础知识相当扎实,编码实现能力也很强。我最欣赏的是我的队友认真和仔细的态度,他对作业近乎追求完美,在满足正确性之后还尽量去优化性能;还有他的坚定的信念,就算他在这周身体不舒服,每天狂咳嗽,但是他还是没有因此落下过一点进度,这是我很敬佩的。

需要改进的地方:在任务分配方面可以均衡一些,不然会累着他自己。

3.6、 感想

这次作业碰上了我重感冒的时候,一直咳嗽不止(不敢熬夜了),再加上自己编程能力不突出,所以时间上安排的不好,对于附加部分的需求,没有时间去考虑和设计。这两次都是结对任务,我和队友之间的协作能力有了进步,但是目前还是没有很高的效率,可能还没度过学习阶段,依然处于磨合阶段。

3.7、学习进度

这两周的任务都是结对任务,所以以此为契机我们阅读了《构建之法》第四章两人合作,了解到结对编程是极限编程这一思想的具体体现,结对编程有三种形式:

①键盘鼠标式②Ping-pong式③领航员-驾驶员式。

了解到结对编程,因为在结对编程过程中有随时的复审和交流,可以减少犯错,提高解决问题的效率,形成知识传递。

了解到要做好结对编程,需要遵守相同的代码规范,在不同的阶段,不同的人之间要有不同的方式,而且要养成个人良好的生活习惯。

但是我觉得虽然结对编程有很多好处,但是结对编程的两个队友,编程水平不能相差过大,不然可能会造成交流变成了教学,浪费更多的时间,影响效率。

转载于:https://www.cnblogs.com/supermingjun/p/10516141.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值