HNU结对编程之队友代码互评

发布时间 2023-09-19 22:45:33作者: 已灰之木

一、前言

在本次互评中,我有幸审阅了zjx同学的项目。zjx同学的代码风格严谨,对于代码规范的遵循和对于项目需求的理解都让人印象深刻。以下是我对他的项目的评价和建议。

 

二、测试

1. 黑盒测试

我首先进行了黑盒测试,也就是从用户的角度,不考虑程序内部结构和属性,只关注程序的输入与输出结果。

(1) 账户管理功能测试

我首先测试了账户管理功能,这包括账户的登录与退出。我尝试创使用不同的用户名和密码进行登录,并尝试了账户退出。当输入错误时会有提示, 而且切换用户登录的功能也能正常工作。

2) 题目生成功能测试

接着,我测试了题目生成功能。这包括了不同难度、不同类型题目的生成。我设定了多种条件进行试验,无论是高中, 小学还是初中, 程序都能生成符合预期的题目。同时,对于不合法的生成条件(超出范围或切换范围错误),程序也能给出正确的错误提示。

生成的题目相当随机, 其中不乏一些根本做不出来的题目.

2. 白盒测试

接着,我进行了白盒测试,也就是从程序员的角度,考虑程序的内部逻辑结构和运行过程。由于zjx同学的代码量较大, 我只选择了最精髓的部分, 即Lex, sort, IsEquivalent

 

(1) "Lex"函数测试

Lex函数的目的是将输入的字符串 the_question 转换为一个 n-ary 树,类似于语法树。每个树节点代表 the_question 中的一个值或操作符。构建树的方式考虑了操作符的优先级和括号嵌套关系。

// Converts the string `the_question` into a n-ary tree, similar to a syntax tree.
// Each node in the tree represents a value or an operator from `the_question`.
// The tree is built in a way that respects the operator precedence and bracket nesting in `the_question`.
tree::nary_tree<TreeNode> Lex(string the_question) {
  int index=0,status=0,times=0;
  stack<int> status_stack;
  tree::nary_tree<TreeNode> result(TreeNode("root"));
  auto curr_root=result.GetRoot();
  while(++times<200) {
    string curr_word=NextOperator(status,the_question,index);
    if (curr_word=="") break;
    if (status>=0) index+=curr_word.size();
    else status=-status-1;
    if (curr_word==")") {
      SubLevel(0,status,curr_root,status_stack,false);
      if (GetNextWord(the_question,index)!="") 
        SubLevel(GetOperatorStatus(GetNextWord(the_question,index)), status, curr_root, 
                 status_stack, true);
    } else {
      string next_word=GetNextWord(the_question,index);
      index+=next_word.size();
      if (next_word[0]<0x30 || next_word[0]>0x39) {//complex
        int end_bracket_index=GetEndBracket(the_question,index);
        string end_string=GetNextWord(the_question,end_bracket_index);
        if (end_bracket_index==the_question.size() || 
            GetOperatorStatus(end_string)<=status) {
          curr_root=result.insert(curr_root,TreeNode(curr_word+next_word+")"));
          status_stack.push(status);
          if (the_question[index]!='-') status=-1;
        } else {
          AddLevel(result,curr_root,curr_word,status_stack,status,index,next_word);
        }
      } else {
        string undetermined_word=GetNextWord(the_question,index);
        int undetermined_status=GetOperatorStatus(undetermined_word);
        if (undetermined_word=="" || 
            undetermined_status==status || 
            undetermined_status==-1) {
          result.insert(curr_root,TreeNode(curr_word,std::stoi(next_word),kLeaf));
        } else if (undetermined_status<status) {
          result.insert(curr_root,TreeNode(curr_word,std::stoi(next_word),kLeaf));
          SubLevel(undetermined_status,status,curr_root,status_stack,false);
          status=GetOperatorStatus(undetermined_word);
        } else if (undetermined_status>status) {
          AddLevel(result,curr_root,curr_word,status_stack,status,index,next_word);
        }
      }
    }
  }
  return result;
}
View Code
  • 它会逐个读取输入表达式中的字符,分析它们是数字还是操作符。
  • 如果是操作符,它会将操作符放入树的适当位置,考虑到了操作符的优先级和括号的影响。
  • 如果是数字,它将数字也放入树中,确保树的结构正确反映了数学表达式的含义。

(2) "TestIfLegal"函数解析
函数 TestIfLegal 检查 the_question 是否适用于目前登录的用户。如果问题通过以下所有测试,则被视为适用:1. 操作符与用户所在年级相匹配。 2. 它不包含多个连续的 "^" 操作符。 3. 用户之前生成的题目没有包含它。 如果问题合法,则返回 true;否则返回 false。

bool TestIfLegal(Account* the_account,string the_question) {
  bool legal=TestIfHasEssentialOperator(the_account->GetType(),the_question);
  legal|=TestIfMultipleExp(the_question);
  legal|=TestIfDuplicated(the_account,(the_question[0]=='-')?the_question:("+"+the_question));
  return legal;
}

(3) "sort_tree"函数解析
sort_tree的目的是对以 root 为根的 n-ary 树的子树进行排序。排序的具体标准没有在函数签名中指定,而是取决于实际的实现。通常,这个函数用于重新排列树中的节点,以便更轻松地进行处理或更高效的计算。

void sort_tree(std::shared_ptr<tree::nary_tree<TreeNode>::Node<TreeNode>> root) {
  if (!root || root->value.GetType()==kLeaf) return;
  for (const auto& i:root->children) sort_tree(i);
  std::sort(root->children.begin(), root->children.end(), 
    [](const std::shared_ptr<tree::nary_tree<TreeNode>::Node<TreeNode>>& a,
      const std::shared_ptr<tree::nary_tree<TreeNode>::Node<TreeNode>>& b) {
      if (a->value!=b->value) {
        return a->value<b->value;
      } else {
        if (a->children.size()!=b->children.size())
          return a->children.size()<b->children.size();
        for (size_t i=0;i<a->children.size();++i) {
          if (a->children[i]->value!=b->children[i]->value)
            return a->children[i]->value<b->children[i]->value;
         }
        return false;
       }
    }
  );
}
View Code
  1. 首先,函数会检查根节点 root 是否存在且不是叶子节点。如果不存在或者是叶子节点,它将不做任何操作。

  2. 接着,它会递归地对所有子节点进行排序,确保整棵子树的节点都被处理。

  3. 最重要的部分是使用 std::sort 函数对子节点进行排序。排序规则由一个函数来定义,这个函数会比较两个节点,决定它们的顺序。

  4. 比较的规则是:首先,根据节点的值来排序,小的排在前面。如果节点的值相同,就比较它们子节点的数量,少的排在前面。如果子节点数量也相同,就逐个比较它们的子节点,直到找到不同的节点为止。

  5. 最终,这个函数会将树的子节点按照上述规则重新排列,使得整棵树按照一定的次序组织起来。

三、评价与建议

1. 代码风格和规范

zjx同学的代码风格严谨,遵循了Google的编码规范,包括行数要求、类的继承关系、函数注释、命名规则等。他的代码整洁有序,易于阅读和理解。

在注释方面,zjx同学还细心地为代码编译选项添加了注释:

/**

 * @file main.cpp

 * @brief This is the main file.

 *

 * This file uses UTF-8 encoding for the source code and GBK encoding

 * for the execution. When compiling, use the following options:

 * -finput-charset=UTF-8 -fexec-charset=GBK

 */

 

2. nary_tree.h文件

zjx同学编写的这个多叉树文件非常出色,他实现的模板类nary_tree功能强大,显示了他在数据结构和算法设计上的深厚功底。不愧是能手写编译器的人

3. 题目生成方式

zjx同学的题目生成方式新颖,他采用生成后判断合法的方式,真正做到了随机生成. 这种方式更加公平和随机,能够生成更多样化的题目。

虽然zjx同学的项目非常出色,但还有一些可以改进的地方:

zjx同学的main函数缺少退出程序的功能,这可能会在某些情况下导致程序难以结束。建议添加退出程序的功能。

四、总结

这次的互评过程让我对zjx同学的编程技能有了更深的认识。他在项目中展现出的代码规范性、创新性思维以及对需求的深入理解都让人印象深刻。尽管有些小的问题需要改进,但这都不影响他的项目整体质量。我希望这次的互评能对他有所帮助,也期待在未来的学习、工作中,我们能有更多的交流和学习。