軟件工程第5次做業(第2次結對做業)——駕駛員與領航員

項目地址

此項目的Github地址爲:
Origin分支 - 擁有者:Rafael Gu
駕駛員:顧苡銘
Fork分支 - 擁有者:Oberon Zheng
領航員:鄭竣太html

題目要求

剛開始上課的時候介紹過一個小學四則運算自動生成程序的例子,請實現它,要求:git

  • 可以自動生成四則運算練習題
  • 能夠定製題目數量
  • 用戶能夠選擇運算符
  • 用戶設置最大數(如十之內、百之內等)
  • 用戶選擇是否有括號、是否有小數
  • 用戶選擇輸出方式(如輸出到文件、打印機等)
  • 最好能提供圖形用戶界面(根據本身能力選作,以完成上述功能爲主)

代碼審查(Code Investigation)

詳見http://www.javashuo.com/article/p-fpcrabmf-kb.htmlgithub

審查基本信息

  • 功能模塊名稱:表達式生成器
  • 審查人:鄭竣太
  • 審查日期:2019年4月23日
  • 代碼名稱:main.cpp
  • 代碼做者:顧苡銘

聲明工做與代碼文件管理(Declaration & Code Management)

審查項 重要 評估 備註
using是否包含了全部可能使用的庫?
using是否包含多餘的庫(未使用的) × 有些using由項目模板自動生成
版權和版本聲明是否完整? × × 此代碼用於課程設計,沒有重視版本控制
類代碼文件名與類的名是否保持一致 × √(Partly) 自定義類庫中一個cs文件包含了多個類,這些類之間存在繼承和引用關係
是否經過提出公共子式的方式將一些並列的做用域下同種用途的局部變量聲明提到做用域外 ×
設計器文件與窗體代碼是否保持對應關係並一致
是否擅自修改了設計器代碼(本應當由設計器自動生成) ×

代碼風格(Code Style)

審查項 重要 評估 備註
空行是否得體? ×
長行拆分是否得體? × -
{} 是否各佔一行而且對齊於同一列? ×
一行代碼是否只作一件事?如只定義一個變量,只寫一條語句。
ifforwhiledo等語句自佔一行,不論執行語句多少都要加 {}
註釋是否有錯誤或者可能致使誤解? ×
類結構的public, protected, private順序是否在全部的程序中保持一致? ×

命名規範(Naming Convention)

審查項 重要 評估 備註
命名規則是否與所採用的操做系統或開發工具的風格保持一致? × 窗體控件的名稱不符合IDE的提倡格式(提倡PascalCase但使用了camelCase)但仍然符合某種規範
標識符是否直觀且能夠拼讀? × 但縮寫較多
標識符的長度應當符合「min-length && max-information」原則? × 僅限於知足上一條的內容
程序中是否出現相同的局部變量和所有變量? 局部變量名稱可能和某函數的參數名相同,但這不影響
類名、函數名、變量和參數、常量的書寫格式是否遵循必定的規則? ×
靜態變量、全局變量、類的成員變量是否加前綴? × ×

表達式與基本語句 (Expressions & Statements)

審查項 重要 評估 備註
若是代碼行中的運算符比較多,是否已經用括號清楚地肯定表達式的操做順序?
是否編寫太複雜或者多用途的複合表達式? × ×
是否將複合表達式與「真正的數學表達式」混淆? × ×
是否用隱含錯誤的方式寫if語句? ×
case語句的結尾是否忘了加break × C#中case後不加break是語法錯誤
是否忘記寫switchdefault分支? × default分支用於拋出異常

常量(Constants)

審查項 重要 評估 備註
是否使用含義直觀的常量來表示那些將在程序中屢次出現的數字或字符串? × ×

函數(Functions)

審查項 重要 評估 備註
參數的書寫是否完整?不要貪圖省事只寫參數的類型而省略參數名字。 × C#不容許省略參數名
參數命名、順序是否合理? ×
參數的個數是否太多? × ×
是否使用類型和數目不肯定的參數? × 使用了param參數表
函數名字與返回值類型在語義上是否衝突? × ×
是否將正常值和錯誤標誌混在一塊兒返回?正常值應當用輸出參數得到,而錯誤標誌用return語句返回。 × 這些函數沒有返回錯誤標誌,正常值直接返回得出,發生錯誤直接拋出異常
在函數體的「入口處」,是否用assert對參數的有效性進行檢查? × 沒有使用assert
濫用了assert? 例如混淆非法狀況與錯誤狀況,後者是必然存在的而且是必定要做出處理的。 × - 同上
是否在發生預料以外的情形下拋出了異常

內存管理(Memory Management)

審查項 重要 評估 備註
是否忘記爲數組和動態內存賦初值?(防止將未被初始化的內存做爲右值使用) - 同上
數組或容器的下標是否越界? ×
動態內存的申請與釋放是否配對?(防止內存泄漏) - C#存在GC機制
是否有效地處理了「內存耗盡」問題? × 但判斷了耗盡情形
是否對局部生成的對象使用了using × ×

C#函數特性

審查項 重要 評估 備註
重載函數是否有二義性? ×
是否混淆了成員函數的重載、覆蓋與隱藏? ×
運算符的重載是否符合制定的編程規範? - 沒有重載運算符
傳引用時是否正確地使用了refout - 沒有傳引用
提供默認參數的參數是否被列到最後
動態參數列表是否被列到最後

類(Classes)

審查項 重要 評估 備註
構造函數中是否遺漏了某些初始化工做? ×
是否正確地使用構造函數的初始化表?
是否錯寫、錯用了構造函數和? ×
是否違背了繼承和組合的規則? ×
是否使用了屬性(Properties)封裝了私有的成員或只讀的值 但不徹底
是否使用單例模式替代靜態類 × ×
是否混淆使用了virtualabstract ×

其它常見問題(Miscellaneous)

審查項 重要 評估 備註
變量的數據類型有錯誤嗎? ×
存在不一樣數據類型的賦值嗎? ×
存在不一樣數據類型的比較嗎? ×
變量的初始化或缺省值有錯誤嗎? ×
變量發生上溢或下溢嗎? ×
變量的精度夠嗎?
因爲精度緣由致使比較無效嗎? ×
表達式中的優先級有誤嗎? ×
邏輯判斷結果顛倒嗎? ×
循環終止條件是否正確?
忘記進行錯誤處理嗎? ×
錯誤處理程序塊一直沒有機會被運行? 部分處理程序沒有執行機會
錯誤處理程序塊自己就有問題嗎?如報告的錯誤與實際錯誤不一致,處理方式不正確等等 ×
文件以不正確的方式打開嗎? ×
沒有正確地關閉文件嗎? ×
發生類型強轉時,是否使用類推薦的轉換函數或裝拆箱替代強轉 ×
事件訂閱時是否充分利用EventArgs類型對象進行數據傳遞 × 事件訂閱沒有傳遞任何數據
是否使用了非安全編程代碼(例如在C#中使用指針) × .NET託管下沒有啓用非安全代碼
是否對可能發生溢出的情形指定了uncheck ×

單元測試(Unit Testing)

因爲WinForm的代碼大部分由IDE的設計器自動生成,所以這裏着重測試類庫MyExpression的代碼
功能類庫MyExpression包含兩個類,其一是用於表示表達式的ExprBase類及其附屬的派生類,其二是用於生成表達式的ExprBuilder類。算法

using Microsoft.VisualStudio.TestTools.UnitTesting;
using MyExpression;
using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyExpression.Tests
{
    [TestClass()]
    public class ExprBuilderTests
    {
        [TestMethod]
        public void AtomExprTest()
        {
            AtomExpr aexp = new AtomExpr(12.5);
            Assert.IsNotNull(aexp);
            Assert.AreEqual(aexp.val, 12.5, double.Epsilon);
            Assert.AreEqual(aexp.Sign, 1);
            Assert.AreEqual(aexp.Priority, int.MaxValue);
            Assert.IsTrue(aexp.Associative);
            Assert.IsTrue(aexp.IsNaturalOrder);
            Assert.AreEqual(aexp.ToString(), "12.5");
        }

        [TestMethod]
        public void ExprTest()
        {
            Expr expEmpty = new Expr();
            Expr expAdd = new Expr(ExprOprt.ADD, new AtomExpr(1.25), new AtomExpr(8.75));
            Expr expSub = new Expr(ExprOprt.SUB, new AtomExpr(3.7), new AtomExpr(4.4));
            Expr expMul = new Expr(ExprOprt.MUL, new AtomExpr(0.25), new AtomExpr(0.3));
            Expr expDiv = new Expr(ExprOprt.DIV, new AtomExpr(0.8), new AtomExpr(2));
            Expr expCplxUnnatural = new Expr(ExprOprt.DIV, expSub, expAdd);
            Expr expCplxNatural = new Expr(ExprOprt.SUB, expMul, expDiv);
            Assert.IsNull(expEmpty.expr0);
            Assert.IsNull(expEmpty.expr1);

            Assert.AreEqual(expEmpty.oprt, ExprOprt.NIL);
            Assert.AreEqual(expAdd.oprt, ExprOprt.ADD);
            Assert.AreEqual(expSub.oprt, ExprOprt.SUB);
            Assert.AreEqual(expMul.oprt, ExprOprt.MUL);

            Assert.IsNotNull(expAdd.expr0);
            Assert.IsNotNull(expAdd.expr1);
            Assert.IsNotNull(expSub.expr0);
            Assert.IsNotNull(expSub.expr1);
            Assert.IsNotNull(expMul.expr0);
            Assert.IsNotNull(expMul.expr1);
            Assert.IsNotNull(expDiv.expr0);
            Assert.IsNotNull(expDiv.expr1);

            Assert.AreEqual(expAdd.ParseValue(), 10.0, double.Epsilon);
            Assert.AreEqual(expSub.ParseValue(), -0.7, double.Epsilon);
            Assert.AreEqual(expMul.ParseValue(), 0.075, double.Epsilon);
            Assert.AreEqual(expDiv.ParseValue(), 0.4, double.Epsilon);
            Assert.AreEqual(expCplxUnnatural.ParseValue(), -0.07, double.Epsilon);
            Assert.AreEqual(expCplxNatural.ParseValue(), -0.325, double.Epsilon);

            Assert.AreEqual(expAdd.Priority, 0);
            Assert.AreEqual(expSub.Priority, 0);
            Assert.AreEqual(expMul.Priority, 1);
            Assert.AreEqual(expDiv.Priority, 1);


            Assert.IsTrue(expAdd.Associative);
            Assert.IsFalse(expSub.Associative);
            Assert.IsFalse(expDiv.Associative);
            Assert.IsTrue(expMul.Associative);
            Assert.IsFalse(expCplxUnnatural.Associative);
            Assert.IsFalse(expCplxNatural.Associative);

            Assert.IsTrue(expAdd.IsNaturalOrder);
            Assert.IsTrue(expSub.IsNaturalOrder);
            Assert.IsTrue(expMul.IsNaturalOrder);
            Assert.IsTrue(expDiv.IsNaturalOrder);
            Assert.IsFalse(expCplxUnnatural.IsNaturalOrder);
            Assert.IsTrue(expCplxNatural.IsNaturalOrder);

            Assert.AreEqual(expAdd.ToString(), "1.25+8.75");
            Assert.AreEqual(expSub.ToString(), "3.7-4.4");
            Assert.AreEqual(expMul.ToString(), "0.25*0.3");
            Assert.AreEqual(expDiv.ToString(), "0.8/2");
            Assert.AreEqual(expCplxNatural.ToString(), "0.25*0.3-0.8/2");
            Assert.AreEqual(expCplxUnnatural.ToString(), "(3.7-4.4)/(1.25+8.75)");
        }

        [TestMethod()]
        public void ExprUtilTest()
        {
            Assert.IsTrue(ExprUtil.GetOprtAssociative(ExprOprt.ADD));
            Assert.IsTrue(ExprUtil.GetOprtAssociative(ExprOprt.MUL));
            Assert.IsFalse(ExprUtil.GetOprtAssociative(ExprOprt.SUB));
            Assert.IsFalse(ExprUtil.GetOprtAssociative(ExprOprt.DIV));

            Assert.AreEqual(ExprUtil.GetOprtChar(ExprOprt.ADD), '+');
            Assert.AreEqual(ExprUtil.GetOprtChar(ExprOprt.SUB), '-');
            Assert.AreEqual(ExprUtil.GetOprtChar(ExprOprt.MUL), '*');
            Assert.AreEqual(ExprUtil.GetOprtChar(ExprOprt.DIV), '/');

            Assert.AreEqual(ExprUtil.GetOprtPriority(ExprOprt.ADD), 0);
            Assert.AreEqual(ExprUtil.GetOprtPriority(ExprOprt.SUB), 0);
            Assert.AreEqual(ExprUtil.GetOprtPriority(ExprOprt.MUL), 1);
            Assert.AreEqual(ExprUtil.GetOprtPriority(ExprOprt.DIV), 1);
        }
        [TestMethod()]
        public void ExprBuilderTest()
        {
            Random r = new Random();
            ExprBuilder eBuilder = new ExprBuilder();
            eBuilder.allowOprt = 0x00;
            eBuilder.Allow(ExprOprt.ADD);
            Assert.AreEqual(eBuilder.allowOprt, 0x01);
            eBuilder.Allow(ExprOprt.DIV);
            Assert.AreEqual(eBuilder.allowOprt, 0x09);
            eBuilder.Disallow(ExprOprt.ADD);
            Assert.AreEqual(eBuilder.allowOprt, 0x08);
            eBuilder.Allow(ExprOprt.ADD);
            eBuilder.Allow(ExprOprt.MUL);
            eBuilder.Allow(ExprOprt.SUB);
            var e = eBuilder.Generate(r);
            Regex regex = new Regex(@"^(\d+)(\.\d+)?[\+|\-|\*|\/](\d+)(\.\d+)?$");
            Assert.IsTrue(regex.IsMatch(e.ToString()));
            eBuilder.allowDec = true;
            eBuilder.maxPrecision = 5;
            e = eBuilder.Generate();
            Assert.IsTrue(regex.IsMatch(e.ToString()));
            eBuilder.allowNeg = true;
            e = eBuilder.Generate();
            regex = new Regex(@"^(((\d+)(\.\d+)?)|(\(\-(\d+)(\.\d+)?\)))[\+|\-|\*|\/](((\d+)(\.\d+)?)|(\(\-(\d+)(\.\d+)?\)))$");
            Assert.IsTrue(regex.IsMatch(e.ToString()));
            regex = new Regex(@"^\([^\(\)]*(((?'g'\()[^\(\)]*)+((?'-g'\))[^\(\)]*)+)*(?(g)(?!))\)$");
            eBuilder.maxOpnd = 5;
            eBuilder.allowCplxExpr = true;
            eBuilder.allowBrack = true;
            e = eBuilder.Generate();
            Assert.IsTrue(regex.IsMatch(String.Format("({0})",e)));
        }
    }
}

評估(Assessment)

程序運行結果

單元測試結果

總結

選題一時爽,結題火葬場express

選題時候這道題和電梯算法那題徘徊不定,主要緣由是:編程

  • 好像都有想法該怎麼作!
  • 又好像徹底沒有想法該怎麼測!!

最終考量到這道題表達的含義更加貼(我並不常常坐電梯),因而就選了這題……數組

而後就是萬劫不復深淵之伊始!!!安全

當時選擇此題的時候,我和個人駕駛員都考慮了好多東西:dom

  1. 首先能夠生成基本的二元四則運算
  2. 而後能夠生成多元的四則運算
  3. 表達式的生成過程能夠考慮使用字符串(參考曾經還有所殘留的編譯原理知識)
  4. 而後再選擇性的套個括號號,美滋滋~

OK好了準備開工,而後咱們驚奇的發現:函數

NAIVE as we are!

  • 除以常數0怎麼辦??
  • 除以了一個括號表達式,而這個括號表達式被優先計算結果也是零(隱含的0)怎麼辦??
  • 若是不容許負數,那麼一旦出現被優先計算的減法表達式的結果是個負數該怎麼辦??
  • 若是某個位置原本不須要括號卻出現了多餘的括號怎麼辦??
    ……

趁着五一假期的時間,我經過遠程桌面的方式引導駕駛員寫代碼,而後常常性的發生這樣的一幕:

駕駛員與領航員看着面前的岔路口不知所措,一臉懵懵地四目相對,留下車裏的X德導航的遊標在地圖上凌亂……

並且,做爲領航員,更加令我感受到窒息和智熄的問題是:

這玩意徹底隨機生成,長度不固定,括號不固定,值也不固定,我該怎麼測試呢??
我總不能把全部的隨機種子全丟進去(數量大體爲int的長度)試一次吧??
就算我這麼作,隨機深度加大以後的數據我是否是也應該測呢??

是的,此次尤爲使人感受到頭大的問題就是測試數據當如何選擇方能達到斷定覆蓋或者條件覆蓋

總之,上述的經歷給了我一個十分慘痛的教訓——

對問題的低估和不得當的規劃是真的要命!

不過好在,在兩我的的重整旗鼓之下,最終仍是作出來了,並且效果還算不錯,最終也完成了測試的過程,可喜可賀,可喜可賀……
……
……了嗎??

說實話,這個程序如今仍然存在一個很是隱蔽的問題(我在作單元測試的時候發現了,是一個很是偶然性的問題),只是我仍是沒想到一個更好的方法去解決這個問題,儘管這個問題通過發現並不影響題目的正確生成,實屬我的能力以及時間有限……

總的來講,這個做業成功地毀掉了個人五一假期同時也讓我明白了不少事情,確確實實是一個很是好的教訓。

以後我也會好好考慮並看看另一道題的。

以上。

相關文章
相關標籤/搜索