結對編程(先後端實現)

 

隊員:鄭偉金 3117004680、陳俊鋒 3117004647css

友情連接: 結對編程(先後端實現) 之 前端篇html

 

一 、Github項目地址前端

 https://github.com/S-TRAVELER/JFWebServerios

2、PSP表格git

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 20 20
· Estimate · 估計這個任務須要多少時間 20 20
Development 開發 1500 1485
· Analysis · 需求分析  90 80
· Design Spec · 生成設計文檔 60 50
· Design Review · 設計複審  30 45
· Coding Standard · 代碼規範 90 60
· Design · 具體設計 60 60
· Coding · 具體編碼 950 1000
· Code Review · 代碼複審 100 120
· Test · 測試(自我測試,修改代碼,提交修改) 120 90
Reporting 報告 150 140
· Test Report · 測試報告 60 50
· Size Measurement · 計算工做量 30 20
· Postmortem & Process Improvement Plan · 過後總結, 並提出過程改進計劃 60 70
Total  總計 1670 1645


3、效能分析github

 1. 題目生成優化:

原來生成題目是經過生成隨機數,來組成分子和分母;同時爲了使分子分母的最大公約數爲1,須要求最大公約數。而後再進行查重和排序,花費時間不少。後來進行優化,再也不經過隨機數取餘的方法,而是經過最簡分數的規律去生成最簡的真分數,而後能夠生成分數的匹配,能夠有效地避免查重和排序的操做,從而減小花費的時間。ajax

優化前:生成1w 道題目並寫入文件共花費了約 1.14s;正則表達式

優化後:生成1w 道題目並寫入文件共花費了約 0.06s ,優化效果明顯。編程

 

 2.  提取題目優化:

原來是瀏覽器請求一次,就生成一次題目;後來改成把全部題目存在內存中,並在瀏覽器請求時,從內存提取數據並返回給瀏覽器。除此以外,因爲題目有要求限制題目的最大數字不能超過某個數字(例如n),因此經過按式子的最大值進行由小到大的排序(式子在生成時已完成,不需生成後再排序),而後記錄每一個最大值的位置以在瀏覽器請求時,快速肯定上界。有效的減小提取式子的時間。後端

   

4、設計過程 

看到題目時,爲小學生提供四則運算的訓練,咱們很快想到以網頁的形式實現,由於從用戶來講,網頁能夠提供更好更快捷的使用體驗。其次,也是爲貼合咱們的學習方向。因此,咱們使用前端和C++後臺的方式完成項目。

功能概覽:

1. 設計實現過程: 

原型設計圖 

 

2. 代碼說明:

前端:

下面挑出前端部分比較有意思的代碼進行說明

// 上傳文件校對答案的部分代碼
this.$form.on('change', '#file', function(e) {
      $(this).clone().replaceAll(_this.file = this)
      _this.setForm()

      _this.currentProgress = 0

      var clock = setInterval(function() {
        $.ajax({
          url: '/check/rate',
          type: 'GET',
          headers: {id: _this.userId}
        })
        .done(function(res) {
          if (res != '100') {
            let rate = parseInt(res)
            if (rate <= 50) {
              _this.$rate[0].innerText = res + '%'
              _this.$rightCircle.css('transform', `rotate(${-135+180*rate/50}deg)`)
              _this.$leftCircle.css('transform', 'rotate(135deg)')
            } else {
              _this.$rate[0].innerText = res + '%'
              _this.$rightCircle.css('transform', 'rotate(45deg)')
              _this.$leftCircle.css('transform', `rotate(${(135+180*rate/100)}deg)`)
            }
          }
          else if (res === '100') {
            $('.downLoad h2')[0].innerText = '點擊下載'
            _this.$rate[0].innerText = res + '%'
            _this.$rightCircle.css('transform','rotate(45deg)')
            _this.$leftCircle.css('transform', 'rotate(315deg)')
            _this.$downLoadBtn.addClass('ready')
            var xhr = new XMLHttpRequest()
            xhr.open('GET', '/check/answer', true)
            xhr.responseType = 'blob'
            xhr.setRequestHeader('id', _this.userId)
            xhr.onload = function() {
              if (this.status === 200) {
                var blob = this.response
                _this.a = document.createElement('a')
                _this.a.download = 'Result.txt'
                _this.a.href = window.URL.createObjectURL(blob)
              }
            }
            xhr.send()
            clearInterval(clock)
          }
        })
        .fail(function(err) {
          console.log(err)
        })
      }, 500)
    }) 
  },
  setForm: function() {
    var _this = this
    var form = document.getElementById('uploadForm')
    console.log(form)
    var formData = new FormData(form)
    formData.append('file', $('#file')[0].files[0])
    $.ajax({
      url: '/check/question',
      type: 'POST',
      data: formData,
      headers: { id: _this.userId },
      processData: false,
      contentType: false
    })
    .done(function(res) {
      console.log(res)
    })
    .fail(function() {
      console.log("error..")
    })
    .always(function() {
      $('#file').replaceWith(_this.file)
    })
  }

上傳文件是發送請求中遇到的較爲困難的一個:

一、input [type=file] 控件選擇同一個文件以後不觸發 change 事件,刷新瀏覽器以後仍不能觸發,若是用戶選擇屢次選擇相同文件名上傳的話會出現文件選擇框不彈出來的 bug

  緣由:由於 input [type=file] 控件的 change 事件是經過 input 輸入框中的文本改變來觸發事件的,而不是像別的編程語言經過選擇文件來觸發,這就致使屢次選擇相同的文件,第一次以後就沒法彈出文件選擇框;還有瀏覽器會自動保存 input [type=file] 控件上的文字,頁面關閉後打開仍然會恢復原來的文本,這時候選擇同路徑的文件也不會觸發 change 事件。

  解決方法:建立一個新的 input [type=file] 控件將舊的替換掉,事件綁定經過事件冒泡來獲取

二、提交表單數據,上網找了一下找到了 form 表單封裝成 formdata 對象的上傳方法,可是會附加額外的信息增長後端處理的負擔。

 

後端:

下面代碼展現了分數以及分數操做的封裝,代碼中展現了使用宏去實現代碼複用,實現分數大小於比較操做。

(因爲代碼較多,摺疊了,有興趣的能夠點開看一下)

class Fraction
{
public:
    friend ostream& operator <<(ostream &os, Fraction &);  
    friend Fraction &operator >>(const string &str, Fraction &that);
    Fraction(int integer = 0, int numerator = 0, int denominator = 1) : _integer(integer),
                                                                        _numerator(numerator),
                                                                        _denominator(denominator)
    {
        if (!(_denominator > 0))
        {
            cout << _integer<< " "<<_numerator<<" "<<_denominator << endl;
            throw runtime_error("denominator must bigger than 0");
        }
    }
    Fraction(const Fraction &that)
        : Fraction(that._integer, that._numerator, that._denominator)
    {}

    Fraction(const std::pair<int, int> &that,int integer=0):
        Fraction(integer,that.first,that.second){}

    static Fraction parse(const string &str)
    {
        size_t pos = str.find('\'');
        size_t oldPos;
        int tmp_integer = 0, tmp_numerator = 0, tmp_denominator = 1;
        if (pos != str.npos)
        {
            tmp_integer = atoi(str.substr(0, pos).c_str());
        }
        if (pos == str.npos)
        {
            oldPos = 0;
            pos = 0;
        }
        else
        {
            oldPos = pos + 1;
        }

        pos = str.find('/', pos);
        if (pos != str.npos)
        {
            // cout << "str:" << str << endl;
            tmp_numerator = atoi(str.substr(oldPos, pos - oldPos).c_str());
            tmp_denominator = atoi(str.substr(pos + 1).c_str());
        }
        else
        {
            tmp_integer = atoi(str.c_str());
        }

        return Fraction(tmp_integer, tmp_numerator, tmp_denominator);
    }
    void setInteger(int integer = 0, bool nozore=false)
    {
        _integer = integer;
        if(_integer==0&&nozore&&_numerator==0){
            if(_denominator==1){
                _integer=1;
            }else{
                _numerator=1;
            }
        }
    }

    void set(int integer = 0, int numerator = 0, int denominator = 1)
    {
        _integer = integer;
        _numerator = numerator;
        _denominator = denominator;
    }

    int gcd(int a, int b)
    {
        int c = a;
        if (b < a)
        {
            a = b;
            b = c;
        }
        while (b)
        {
            c = a % b;
            a = b;
            b = c;
        }
        return a;
    }

    void operator =(const Fraction &that){
        _integer=that._integer;
        _numerator=that._numerator;
        _denominator=that._denominator;
    }

    operator std::string()
    {
        char buffer[MAX_FRACTION_LEN]{'\0'};
        if (_denominator == 1)
        {
            sprintf(buffer, "%d", _integer + _numerator);
        }
        else if (_numerator == 0)
        {
            sprintf(buffer, "%d", _integer);
        }
        else if (_integer == 0)
        {
            sprintf(buffer, "%d/%d", _numerator, _denominator);
        }
        else
        {
            sprintf(buffer, "%d'%d/%d", _integer, _numerator, _denominator);
        }
        return string(buffer);
    }

    Fraction operator+(const Fraction &that)
    {
        int tmp_denominator, tmp_numerator;
        if (that._numerator == 0)
        {
            tmp_denominator = this->_denominator;
            tmp_numerator = this->_numerator;
        }
        else
        {
            int tmp_gcd = gcd(that._denominator, this->_denominator);
            tmp_denominator = that._denominator * this->_denominator / tmp_gcd;
            tmp_numerator = that._numerator * this->_denominator / tmp_gcd + this->_numerator * that._denominator / tmp_gcd;
        }

        if (tmp_numerator > tmp_denominator)
        {
            return Fraction(that._integer + this->_integer + 1, tmp_numerator - tmp_denominator, tmp_denominator);
        }
        else
        {
            return Fraction(that._integer + this->_integer, tmp_numerator, tmp_denominator);
        }
    }

    Fraction operator-(const Fraction &that)
    {
        int tmp_denominator, tmp_numerator;
        if (that._denominator == 0)
        {
            tmp_denominator = this->_denominator;
            tmp_numerator = this->_numerator;
        }
        else
        {
            int tmp_gcd = gcd(that._denominator, this->_denominator);
            tmp_denominator = that._denominator * this->_denominator / tmp_gcd;
            tmp_numerator = this->_numerator * that._denominator / tmp_gcd - that._numerator * this->_denominator / tmp_gcd;
        }

        int this_integer = this->_integer;
        if (tmp_numerator < 0)
        {
            tmp_numerator += tmp_denominator;
            this_integer -= 1;
        }

        return Fraction(this_integer - that._integer, tmp_numerator, tmp_denominator);
    }

    Fraction operator/(const Fraction &that)
    {
        int this_numerator = this->_numerator + this->_integer * this->_denominator;
        int that_numerator = that._numerator + that._integer * that._denominator;

        if(that_numerator==0){
            throw runtime_error("除數不能爲0");
        }

        int tmp_denominator, tmp_numerator;
        if (this_numerator == 0)
        {
            tmp_denominator = 1;
            tmp_numerator = 0;
        }
        else
        {
            tmp_denominator = that_numerator * this->_denominator;
            tmp_numerator = that._denominator * this_numerator;
            int tmp_gcd = gcd(tmp_denominator, tmp_numerator);
            tmp_denominator /= tmp_gcd;
            tmp_numerator /= tmp_gcd;
        }
        int &&tmp_integer = tmp_numerator / tmp_denominator;

        return Fraction(tmp_integer, tmp_numerator - tmp_integer * tmp_denominator, tmp_denominator);
    }

    Fraction operator*(const Fraction &that)
    {
        int this_numerator = this->_numerator + this->_integer * this->_denominator;
        int that_numerator = that._numerator + that._integer * that._denominator;

        int tmp_denominator, tmp_numerator;
        if (that_numerator == 0)
        {
            tmp_denominator = 1;
            tmp_numerator = 0;
        }
        else
        {
            tmp_numerator = that_numerator * this_numerator;
            tmp_denominator = that._denominator * this->_denominator;
            int tmp_gcd = gcd(tmp_denominator, tmp_numerator);
            tmp_denominator /= tmp_gcd;
            tmp_numerator /= tmp_gcd;
        }

        int &&tmp_integer = tmp_numerator / tmp_denominator;

        return Fraction(tmp_integer, tmp_numerator - tmp_integer * tmp_denominator, tmp_denominator);
    }
 /*-----------------------------使用宏實現代碼複用--------------------------*/

#define FRACTION_BINARY_PREDICATE(cmp, auxcmp)               \
    bool operator cmp(const Fraction &that)                    \
    {                                                          \
        int r = _integer - that._integer;                       \
        int d = compare(that._numerator, that._denominator);     \
        return ((r auxcmp 0) || ((r == 0) && (d cmp 0)));         \
    }
    FRACTION_BINARY_PREDICATE(<, <)
    FRACTION_BINARY_PREDICATE(<=, <)
    FRACTION_BINARY_PREDICATE(>=, >)
    FRACTION_BINARY_PREDICATE(>, >)

    bool operator==(const Fraction &that)
    {
        int r = this->_integer - that._integer;
        int d = compare(that._numerator, that._denominator);
        return (((r == 0) && (d == 0)));
    }

private:
    inline int compare(int numerator, int denominator)
    {
        return this->_numerator * denominator - numerator * this->_denominator;
    }

private:
    int _integer;      //整數
    int _numerator;    //分母
    int _denominator;  //分子
};

ostream& operator <<(ostream &os, Fraction &that){
    return os<<string(that);
}

Fraction &operator >>(const string &str, Fraction &that)
{
    that=Fraction::parse(str);
    return that;
}

 四則運算:

Fraction Calculator::onCompute(bool &onErr){
    bool exit=false;
    onErr=false;

    for(auto &it:_keys)
    {
       

        const string &tmp = it;

        char op;

        if (tmp[0] >= '0' && tmp[0] <= '9')
        {
            _nums.push_back(Fraction::parse(tmp));
        }
        else if (tmp[0] != ' ')
        {
            switch (tmp[0])
            {

            case SUB:
            case ADD:
                if (_operators.size()>0&&_operators.back()==MUL||_operators.back()==DIV||_operators.back()==SUB){
                    while (op != LBR && _operators.size() > 0)
                    {
                        op = _operators.back();
                        _operators.pop_back();
                        if (op != LBR)
                        {
                            calculate(op);
                        }
                    }

                    if (op == LBR)
                    {
                        _operators.push_back(op);
                    }
                }
                _operators.push_back(tmp[0]);

                break;
            case MUL:
            case LBR:
                _operators.push_back(tmp[0]);
                break;
            case RBR:
                while (op != LBR && _operators.size() > 0)
                {
                    op = _operators.back();
                    _operators.pop_back();
                    if (op != LBR)
                    {
                        calculate(op);
                    }
                }
                break;
            case EQU:
                while (_operators.size() > 0)
                {
                    op = _operators.back();
                    _operators.pop_back();
                    if (op != LBR)
                    {
                        calculate(op);
                    }
                }
                exit=true;
                break;
            default:
                if (tmp.compare(divide) == 0)
                {
                    _flag = true;
                    _operators.push_back(DIV);
                }
                else
                {
                    onErr=true;
                    return Fraction();
                }
                break;
            }
        }
        if(exit){
            break;
        }
    }
    if(_nums.size() != 1){
        onErr=true;
        return Fraction();
    }
    return _nums.back();
}

bool Calculator::calculate(char op)
{
    auto tmp2 = _nums.back();
    _nums.pop_back();
    auto tmp1 = _nums.back();
    _nums.pop_back();
    switch (op)
    {
    case ADD:
        _nums.push_back(tmp1 + tmp2);
        break;
    case SUB:
        _nums.push_back(tmp1 - tmp2);
        break;
    case MUL:
        _nums.push_back(tmp1 * tmp2);
        break;
    case DIV:
        _nums.push_back(tmp1 / tmp2);
        break;
    default:
        return false;
    }
    return true;
}

  

5、測試代碼

1. 正確性測試

測試四則計算的準確性

#include "Server/Calculator.h"
#include <assert.h>

using namespace std;
using namespace Server;

int main(){
    {
        Calculator c;
        cout << string(c.compute("12 x 14 + 15 ÷ 3 - 1/23 = ")) << endl;
        cout << string(c.compute("1/2")) << endl;
        cout<< string(c.compute("( 1 + 2 ) x 3 = "))<<endl;
        cout<< string(c.compute("3 x ( 1 + 1'1/2 ) ="))<<endl;
        cout<< "3 x ( 1 + 1'1/2 ) = 7'1/2 "<<(c.check("3 x ( 1 + 1'1/2 ) = 7'1/2")?"true":"false")<<endl;
        string question("1+ 2-3+(1+2)x 5= 1");
        string answer;
        bool result=c.check(question,answer);
        cout<< question<<" "<<(result?"true":"false")<<" answer: "<<answer<<endl;

    }
 
    return 0;
}

  

2. 分數類操做符測試

分數的操做測試

#include <iostream>
#include <assert.h>
#include "Server/Producer.h"

using namespace Server;
using namespace std;

void test_producer(){
    Producer::Instance().generate(10,10);
}
int main()
{
    test_producer();
    {
        Fraction f1(2, 1, 2);
        Fraction f2(2, 1, 3);
        assert(f1 > f2);
        cout << string(f1) << endl;
        cout << string(f2) << endl;

        cout << string(f1 + f2) << endl;
        cout << string(f1 - f2) << endl;
        cout << string(f1 * f2) << endl;
        cout << string(f1 / f2) << endl;
    }
    {
        Fraction f1(1, 1, 2);
        Fraction f2(2, 1, 3);
        Fraction f3(2, 1, 3);
        assert(f3 == f2);
        assert(f2 - f1 == Fraction(0, 5, 6));
    }

    {
        Fraction f1(0, 1, 2);
        Fraction f2(2);
        assert(f1 + f2 == Fraction(2, 1, 2));
        assert(f2 - f1 == Fraction(1, 1, 2));
        assert(f1 * f2 == Fraction(1));
        assert(f1 / f2 == Fraction(0, 1, 4));
    }

    {
        Fraction f1 = Fraction::parse("4'1/2");
        Fraction f2 = Fraction::parse("4");
        Fraction f3 = Fraction::parse("1/2");

        assert(f1 == Fraction(4, 1, 2));
        assert(f2 == Fraction(4));
        assert(f3 == Fraction(0, 1, 2));
    }

    return 0;
}

 

3. 性能分析代碼

觀察花費的時間看,以方便優化。

#include <iostream>
#include <vector>
#include <cstdlib>
#include <boost/timer/timer.hpp>
#include <fstream>
#include "Server/Producer.h"
#include <Server/CalculatorRecord.h>

using namespace Server;
using namespace boost::timer;
using namespace std;

#define LINE_MAX_LENGTH 100

int main(){

    {
        cpu_timer t;

        ofstream qafile("QuestionAndAnswer.txt", ios::out);
        if(qafile.is_open()){
    //獲取問題和答案,getAllQuestionAndAnwsers接口只提供給測試
            Producer::Instance().getAllQuestionAndAnswers(qafile);
            qafile.close();
        }

        cout<<t.format();
    }

    {
        CalculatorRecord cr;

        ifstream qafile("QuestionAndAnswer.txt", ios::in);
        ofstream outfile("Result.txt", ios::out);
        cpu_timer t;

        char strbuf[LINE_MAX_LENGTH+1];
        if(qafile.is_open()&&outfile.is_open()){
    //string::eof接口會在最後一行執行2次,因此用stirng::peek
            while(qafile.peek()!=EOF){ 
     //逐行讀取,並放入運算器中
                qafile.getline(strbuf,LINE_MAX_LENGTH);
                cr.push_back(strbuf);
            }
            qafile.close();
            cr.compute();
            cr.Foreach([&](const string &it){
                outfile<<it<<endl;
            });
        }

        cout<<t.format();
    }

    return 0;
}

  

6、測試結果 

1. 頁面展現

歡迎頁面:

 

 

在線答題:

 

 獲取題目與答案:

 

 上傳文件覈對:

2. 在線答題覈對

答題正確:

答題錯誤或未做答:

 

 

3. 上傳答題覈對

4. 性能

(1)10000道題目生成用時 0.068 s

(2)10000道題目覈對用時 6.809s(因爲爲了支持用戶自定義題目的核對,故採用了正則表達式進行識別,因此識別花費時間比較多,若是想提升速度能夠考慮去除正則匹配以及並行優化)

7、項目心得

此次結對編程的收穫很大,對於合做開發有了更深的理解,從不一樣的技術層面和不一樣的角度去看待項目,每一個人都有不一樣的想法和收穫。與單人項目相比,結對項目有着更多的交流以及想法思緒,畢竟一個好的程序或項目不是一我的的狂歡;與多人項合做相比,結對編程又顯得精小而有力,在開發中也更加團結和迅速。 固然,也很幸運此次的合做能遇到很給力的隊友👍,整個開發過程當中保持密切的交流和積極的熱情,因此開發很高效也很愉快。

與大部分的同窗不一樣,咱們選擇之前後端的形式實現四則運算項目,其一是想切合咱們的學習方向;此外從用戶體驗來講,網頁的形式能夠給用戶提供更好的體驗。

相關文章
相關標籤/搜索