iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 4
0
Software Development

當我遊走在程式的初學路上-從入門到放棄系列 第 7

Project 1 - 自我介紹程式(7):斷開程式碼的牽連 - 重構 (附上完整專案 Github)

  • 分享至 

  • xImage
  •  

複習第六天 - 從讀寫檔的實作過程喚醒3、4天的記憶

一、讀取保存輸入的個人資訊 - 文字類讀寫檔

可以實作的檔案類型包括「TXT、CSV、JSON、XML、XLS」,每一種檔案格式都有對應的IO函式庫。

二、讀取與保存相片檔 - 圖片讀寫檔

在C#,可以使用Image函式庫來處理圖片,或者透過Stream 讀取Byte[]後轉成陣列

三、防範並處理資料讀寫可能造成的錯誤

  1. 讀取檔案結果檔案不存在
  2. 讀取檔案資料,結果因為檔案裡面的資料結構有問題,無法存到變數當中
  3. 將資料寫入檔案,結果資料夾或檔案路徑不存在
  4. 將資料寫入檔案,結果資料為NULL,寫入文字檔的過程中無法轉成文字造成錯誤。寫入圖片檔的過程中無法轉成圖片格式造成錯誤

從教學者與學習者看放棄程式的人

程式就像國文學科一樣,每個人都能寫出一篇作文,但是若要寫出一篇文筆簡潔的文章,需要對國文的修辭、文法、標點符號、成語、詞彙有一定的掌握。

同樣的,要對程式碼重構,對於「資料結構」和「物件導向」需要有非常高的掌握度,對於初學者來說並不容易

第七天簡介:雖然你的程式碼可以動,但是需要整理一下

這次重構要處理的問題:

  1. 重複校驗的程式碼
  2. 封裝多個常用的資料變數
  3. 抽離JSON資料操作的程式碼到別的檔案
  4. 抽離圖片讀寫的程式碼到別的檔案

Github 連結

「自我介紹」程式經過七天的最終程式碼放在Github上,有需要可以自行下載取用
https://github.com/ted59438/2019ITHome

重構後的專案結構

https://ithelp.ithome.com.tw/upload/images/20190909/20120331Dcb4Nw5P8f.png

  • HelperClass:用來處理特定用途的 Static函式方法 程式碼
  • LanguageResource:多語系的專案資源檔
  • ObjectClass:封裝用的物件類別檔
  • View:WinForm的視窗程式檔

重構前後的 IntroductionForm.cs 完整程式碼

先看重構後的程式碼:
https://github.com/ted59438/2019ITHome/blob/master/IT_Day01/View/IntroductionForm.cs

在看重構前的程式碼:
https://github.com/ted59438/2019ITHome/blob/master/IT_Day01/View/IntroductionForm_before7Days.cs

重構點1:重複校驗的程式碼

我們來看看之前的程式碼,首先看到「按下自我介紹 (showIntroductionBtn_Click)」的事件程式碼,再看看 「按下保存個人資訊 (saveBtn_Click)」的事件程式碼。

重構前:

// IntroductionForm.cs

    TextBox[] allTextBox = new TextBox[] { nameTextBox, homeTownTextBox, birthdate_YearBox, birthdate_MonthBox, birthdate_DayBox };
    string[] allTextBoxName = new string[]{LanguageResources.Name, LanguageResources.HomeTown,
                                           LanguageResources.Birthday_Year, LanguageResources.Birthday_Month, LanguageResources.Birthday_Day };

    StringBuilder errorMsg = new StringBuilder();

    for (int i = 0; i < allTextBox.GetLength(0); i++)
    {
        if (string.IsNullOrEmpty(allTextBox[i].Text))
        {
            errorMsg.AppendLine(string.Format(LanguageResources.Message_PleaseInput, allTextBoxName[i]));
        }
    }

    if (errorMsg.ToString() != "")
    {
        MessageBox.Show(errorMsg.ToString(), "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }

    // 校驗日期格式是否正確
    if (!Regex.IsMatch(birthdate_YearBox.Text, @"\d") || !Regex.IsMatch(birthdate_MonthBox.Text, @"\d") || !Regex.IsMatch(birthdate_DayBox.Text, @"\d"))
    {
        MessageBox.Show(LanguageResources.Message_BirthdayNeedNum, "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }

重構後:

這段程式碼被拆成兩個函式方法
checkAllColumnIsNotEmpty():校驗所有欄位是不是都有輸入
checkDateIsValidate(): 校驗日期格式是否正確

// IntroductionForm.cs

private void checkAllColumnIsNotEmpty()
{
    // 校驗每個欄位是否輸入
    TextBox[] allTextBox = new TextBox[] { nameTextBox, homeTownTextBox, birthdate_YearBox, birthdate_MonthBox, birthdate_DayBox };
    string[] allTextBoxName = new string[]{LanguageResources.Name, LanguageResources.HomeTown,
                                           LanguageResources.Birthday_Year, LanguageResources.Birthday_Month, LanguageResources.Birthday_Day };

    StringBuilder errorMsg = new StringBuilder();

    for (int i = 0; i < allTextBox.GetLength(0); i++)
    {
        if (string.IsNullOrEmpty(allTextBox[i].Text))
        {
            errorMsg.AppendLine(string.Format(LanguageResources.Message_PleaseInput, allTextBoxName[i]));
        }
    }

    if (photoBox.Image == null)
    {
        errorMsg.AppendLine(LanguageResources.Message_NoImage);
    }

    if (errorMsg.ToString() != "")
    {
        throw new Exception(errorMsg.ToString());
    }
}

/// <summary>
/// 校驗日期格式是否正確
/// </summary>
private void checkDateIsValidate()
{
    if (!Regex.IsMatch(birthdate_YearBox.Text, @"\d") || !Regex.IsMatch(birthdate_MonthBox.Text, @"\d") || !Regex.IsMatch(birthdate_DayBox.Text, @"\d"))
    {
        throw new Exception(LanguageResources.Message_BirthdayNeedNum);
    }
}

重構點2:封裝多個常用的資料變數

畫面取得「姓名」、「家鄉」、「出生年月日」的程式碼也重複了

重構前

// IntroductionForm.cs

private void showIntroductionBtn_Click(object sender, EventArgs e)
{        
    // ... 前後程式碼省略

    string name = nameTextBox.Text;
    string homeTown = homeTownTextBox.Text;


    int today_Year = DateTime.Today.Year;
    int today_Month = DateTime.Today.Month;
    int today_Day = DateTime.Today.Day;
    
    // ... 前後程式碼省略
}
// IntroductionForm.cs

/// <summary>
/// 按下「保存個人資訊」
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void saveBtn_Click(object sender, EventArgs e)
{
    // ... 前後程式碼省略

    string name = nameTextBox.Text;
    string homeTown = homeTownTextBox.Text;

    int birthDate_Year = int.Parse(birthdate_YearBox.Text);
    int birthDate_Month = int.Parse(birthdate_MonthBox.Text);
    int birthDate_Day = int.Parse(birthdate_DayBox.Text);
    
    // ... 前後程式碼省略
}

重構後

建立一個新的C#類別檔案,取名為「IntroductionOBJ.cs」,把自我介紹的「姓名」、「家鄉」、「出生日期」、「相片」,用一個物件類別「IntroductionOBJ」封裝

// IntroductionOBJ.cs

using System;

namespace IT_Day01
{
    public class IntroductionOBJ
    {
        /// <summary>
        /// 姓名
        /// </summary>
        public string name { get; set; }

        /// <summary>
        /// 家鄉
        /// </summary>
        public string homeTown { get; set; }

        /// <summary>
        /// 生日
        /// </summary>
        public DateTime birthDate { get; set; }

        /// <summary>
        /// 相片
        /// </summary>
        public byte[] photo { get; set; }
    }
}

在 自我介紹主視窗 (IntroductionForm.cs) 撰寫一個方法 (getIntroductionFromView()),每一次從畫面所有的欄位資料取得之後,以IntroductionOBJ 物件 存放

// IntroductionForm.cs

/// <summary>
/// 從畫面上的所有欄位取得自我介紹資訊
/// </summary>
/// <returns></returns>
private IntroductionOBJ getIntroductionFromView()
{
    try
    {
        checkAllColumnIsNotEmpty();
        checkDateIsValidate();

        IntroductionOBJ introductionOBJ = new IntroductionOBJ();

        introductionOBJ.name = nameTextBox.Text;
        introductionOBJ.homeTown = homeTownTextBox.Text;
        introductionOBJ.birthDate = new DateTime(int.Parse(birthdate_YearBox.Text), int.Parse(birthdate_MonthBox.Text), int.Parse(birthdate_DayBox.Text));
        introductionOBJ.photo = PhotoHelper.ImageToBytes(photoBox.Image);

        return introductionOBJ;

    }
    catch (Exception error)
    {
        throw new Exception(error.Message);
    }
}

到目前重構完getIntroductionFromView()後的「按下自我介紹」跟「保存個人資訊」Click 事件的程式碼:

// 按下自我介紹
private void showIntroductionBtn_Click(object sender, EventArgs e)
{
    IntroductionOBJ introductionOBJ = getIntroductionFromView();
    
    // 計算年齡
    int yearOld;
    DateTime todayDate = DateTime.Today;
    
    yearOld = today_Year - introductionOBJ.birthDate.Year;
    if (todayDate.Month < introductionOBJ.birthDate.Month || (todayDate.Month == introductionOBJ.birthDate.Month && todayDate.Day < introductionOBJ.birthDate.Day))
    {
        yearOld = yearOld - 1;
    }

    string introductionText = string.Format(LanguageResources.Message_IntrouductionText, introductionOBJ.name, introductionOBJ.homeTown, yearOld);
    MessageBox.Show(introductionText);
}

/// <summary>
/// 按下「保存個人資訊」
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void saveBtn_Click(object sender, EventArgs e)
{
    try
    {
        IntroductionOBJ introductionOBJ = getIntroductionFromView();
        
        // ... 省略Json寫檔的程式碼
        JObject introductionJson = new JObject();

        introductionJson.Add("Name", introductionOBJ.name);
        introductionJson.Add("HomeTown", introductionOBJ.homeTown);
        introductionJson.Add("BirthDate", 
        string.Format("{0}-{1}-{2}", introductionOBJ.birthDate.Year,                                                          
                                     introductionOBJ.birthDate.Month,                                                           
                                     introductionOBJ.birthDate.Day));

        string dirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data");
        string jsonPath = Path.Combine(dirPath, "Introduction.json");
        string imagePath = Path.Combine(dirPath, "Photo.jpeg");

        // 每次讀寫檔之前,檢查路徑的資料夾與檔案是否存在,避免發生路徑不存在的錯誤
        if (!Directory.Exists(dirPath))
        {
            Directory.CreateDirectory(dirPath);
        }

        // 檔案不存在,產生個人資訊的檔案
        if (!File.Exists(jsonPath))
        {
            File.Create(jsonPath).Close();
        }

        // 保存個人資訊到JSON
        File.WriteAllText(jsonPath, JsonConvert.SerializeObject(introductionJson));
        // 保存個人大頭貼到Jpeg 圖片
        photoBox.Image.Save(imagePath, ImageFormat.Jpeg);

    }
    catch (Exception error)
    {
        MessageBox.Show(error.Message, "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }

}

重構點3: 抽離JSON資料操作的程式碼到別的檔案

新增專門負責讀寫檔的類別「FileHelper.cs」讓 WinForm 的「UI 邏輯」和 「JSON的讀寫操作」彼此不會互相牽扯。

// FileHelper.cs

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Text.RegularExpressions;

namespace IT_Day01
{
    public class FileHelper
    {
        private static string dirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data");
        private static string jsonPath = Path.Combine(dirPath, "Introduction.json");
        private static string imagePath = Path.Combine(dirPath, "Photo.jpeg");

        /// <summary>
        /// 自我介紹 JSON讀檔流程
        /// </summary>
        /// <returns></returns>
        public static IntroductionOBJ processRead()
        {
            IntroductionOBJ introductionOBJ;
            JObject introductionJson;

            if (!prepareRead())
            {
                return new IntroductionOBJ();
            }

            introductionJson = readFromJson();
            if (!checkJsonIsVailed(introductionJson))
            {
                return new IntroductionOBJ();
            }

            introductionOBJ = getIntroductionJsonStr(introductionJson);
            introductionOBJ.photo = PhotoHelper.readImageStreamFromFile(imagePath).ToArray() ;

            return introductionOBJ;
        }

        /// <summary>
        /// 讀檔前的檢查
        /// </summary>
        /// <returns></returns>
        private static bool prepareRead()
        {
            if (!Directory.Exists(dirPath) || !File.Exists(jsonPath) || !File.Exists(imagePath))
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// 讀取JSON文字
        /// </summary>
        /// <returns></returns>
        private static JObject readFromJson()
        {
            string introductionJsonStr = File.ReadAllText(jsonPath);
            JObject introductionJson = (JObject)JsonConvert.DeserializeObject(introductionJsonStr);

            return introductionJson;
            
        }

        /// <summary>
        /// 檢查內容結構是否被破壞
        /// </summary>
        /// <param name="introductionJson"></param>
        /// <returns></returns>
        private static bool checkJsonIsVailed(JObject introductionJson)
        {
            if (introductionJson == null ||
                !introductionJson.ContainsKey("Name") || !introductionJson.ContainsKey("HomeTown") || !introductionJson.ContainsKey("BirthDate"))
            {
                return false;
            }
            else if (!Regex.IsMatch(introductionJson["BirthDate"].ToString().Split('-')[0], @"\d") ||
                     !Regex.IsMatch(introductionJson["BirthDate"].ToString().Split('-')[1], @"\d") ||
                     !Regex.IsMatch(introductionJson["BirthDate"].ToString().Split('-')[2], @"\d"))
            {
                return false;
            }
            else if (!File.Exists(imagePath))
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// 取得自我介紹資訊
        /// </summary>
        /// <param name="introductionJson"></param>
        /// <returns></returns>
        private static IntroductionOBJ getIntroductionJsonStr(JObject introductionJson)
        {
            IntroductionOBJ introductionOBJ = new IntroductionOBJ();

            introductionOBJ.name = introductionJson["Name"].ToString();
            introductionOBJ.homeTown = introductionJson["HomeTown"].ToString();

            introductionOBJ.birthDate = new DateTime(int.Parse(introductionJson["BirthDate"].ToString().Split('-')[0]),
                                                     int.Parse(introductionJson["BirthDate"].ToString().Split('-')[1]),
                                                     int.Parse(introductionJson["BirthDate"].ToString().Split('-')[2]));

            return introductionOBJ;
        }

        /// <summary>
        /// 自我介紹 寫檔流程
        /// </summary>
        /// <param name="introductionOBJ"></param>
        public static void processSave(IntroductionOBJ introductionOBJ)
        {
            prepareWrite();
            writeToJson(introductionOBJ);
            saveImageToFile(introductionOBJ.photo);
        }

        private static void prepareWrite()
        {
            // 每次讀寫檔之前,檢查路徑的資料夾與檔案是否存在,避免發生路徑不存在的錯誤
            if (!Directory.Exists(dirPath))
            {
                Directory.CreateDirectory(dirPath);
            }

            // 檔案不存在,產生個人資訊的檔案
            if (!File.Exists(jsonPath))
            {
                File.Create(jsonPath).Close();
            }
        }

        private static void writeToJson(IntroductionOBJ introductionOBJ)
        {
            JObject introductionJson = new JObject();

            introductionJson.Add("Name", introductionOBJ.name);
            introductionJson.Add("HomeTown", introductionOBJ.homeTown);
            introductionJson.Add("BirthDate", string.Format("{0}-{1}-{2}", introductionOBJ.birthDate.Year,
                                                                           introductionOBJ.birthDate.Month,
                                                                           introductionOBJ.birthDate.Day));

            File.WriteAllText(jsonPath, JsonConvert.SerializeObject(introductionJson));
        }

        private static void saveImageToFile(byte[] imageByte)
        {
            Image newImage = PhotoHelper.bytesToImage(imageByte);
            newImage.Save(imagePath, ImageFormat.Jpeg);
        }
    }
}

如果要保存自我介紹的資訊,可透過先前封裝的「IntroductionOBJ」,從UI 傳到 處理讀寫檔的
以下分別是「自我介紹主畫面載入後(IntroductionForm_Load)」重構前後的程式碼

重構前:

/// <summary>
/// 顯示邀請自我介紹的文字
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void IntroductionForm_Load(object sender, EventArgs e)
{
    loadSaveIntroduction();
    MessageBox.Show(LanguageResources.FormStart);
}

/// <summary>
/// 將自我介紹的資訊顯示到畫面上
/// </summary>
private void loadSaveIntroduction()
{
    string dirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data");
    string filePath = Path.Combine(dirPath, "Introduction.json");
    string imagePath = Path.Combine(dirPath, "Photo.jpeg");

    // 每次讀寫檔之前,檢查路徑的資料夾與檔案是否存在,避免發生路徑不存在的錯誤
    if (!Directory.Exists(dirPath) || !File.Exists(filePath) || !File.Exists(imagePath))
    {
        return;
    }

    // 從JSON檔讀取先前保存的個人資訊
    string introductionJsonStr = File.ReadAllText(filePath);
    JObject introductionJson = (JObject)JsonConvert.DeserializeObject(introductionJsonStr);

    //讀完資料發現資料空白、結構有缺漏、結構被破壞,不要讀取
    if (introductionJson == null || 
        !introductionJson.ContainsKey("Name") || !introductionJson.ContainsKey("HomeTown") || !introductionJson.ContainsKey("BirthDate"))
    {
        return;
    }
    else
    {
        nameTextBox.Text = introductionJson["Name"].ToString();
        homeTownTextBox.Text = introductionJson["HomeTown"].ToString();

        birthdate_YearBox.Text = introductionJson["BirthDate"].ToString().Split('-')[0];
        birthdate_MonthBox.Text = introductionJson["BirthDate"].ToString().Split('-')[1];
        birthdate_DayBox.Text = introductionJson["BirthDate"].ToString().Split('-')[2];
    }

    photoBox.Image = Image.FromFile(imagePath);
}

重構後

/// <summary>
/// 顯示邀請自我介紹的文字
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void IntroductionForm_Load(object sender, EventArgs e)
{
    loadSaveIntroduction();
    MessageBox.Show(LanguageResources.FormStart);
}

/// <summary>
/// 將自我介紹的資訊顯示到畫面上
/// </summary>
private void loadSaveIntroduction()
{
    IntroductionOBJ introductionOBJ = FileHelper.processRead();

    nameTextBox.Text = introductionOBJ.name;
    homeTownTextBox.Text = introductionOBJ.homeTown;

    birthdate_YearBox.Text = introductionOBJ.birthDate.Year.ToString();
    birthdate_MonthBox.Text = introductionOBJ.birthDate.Month.ToString();
    birthdate_DayBox.Text = introductionOBJ.birthDate.Day.ToString();

    if (introductionOBJ.photo != null)
        photoBox.Image = PhotoHelper.bytesToImage(introductionOBJ.photo);
}

以下是「保存個人資訊」 重構前後的程式碼

重構前

/// <summary>
/// 按下「保存個人資訊」
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void saveBtn_Click(object sender, EventArgs e)
{
    try
    {
        IntroductionOBJ introductionOBJ = getIntroductionFromView();
        
        JObject introductionJson = new JObject();

        introductionJson.Add("Name", introductionOBJ.name);
        introductionJson.Add("HomeTown", introductionOBJ.homeTown);
        introductionJson.Add("BirthDate", 
        string.Format("{0}-{1}-{2}", introductionOBJ.birthDate.Year,                                                          
                                     introductionOBJ.birthDate.Month,                                                           
                                     introductionOBJ.birthDate.Day));

        string dirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data");
        string jsonPath = Path.Combine(dirPath, "Introduction.json");
        string imagePath = Path.Combine(dirPath, "Photo.jpeg");

        // 每次讀寫檔之前,檢查路徑的資料夾與檔案是否存在,避免發生路徑不存在的錯誤
        if (!Directory.Exists(dirPath))
        {
            Directory.CreateDirectory(dirPath);
        }

        // 檔案不存在,產生個人資訊的檔案
        if (!File.Exists(jsonPath))
        {
            File.Create(jsonPath).Close();
        }

        // 保存個人資訊到JSON
        File.WriteAllText(jsonPath, JsonConvert.SerializeObject(introductionJson));
        // 保存個人大頭貼到Jpeg 圖片
        photoBox.Image.Save(imagePath, ImageFormat.Jpeg);

    }
    catch (Exception error)
    {
        MessageBox.Show(error.Message, "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }

}

重構後

/// <summary>
/// 按下「保存個人資訊」
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void saveBtn_Click(object sender, EventArgs e)
{
    try
    {
        IntroductionOBJ introductionOBJ = getIntroductionFromView();
        FileHelper.processSave(introductionOBJ);
        MessageBox.Show("保存完成!", "訊息", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }
    catch (Exception error)
    {
        MessageBox.Show(error.Message, "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }

}

重構點4:抽離圖片讀寫的程式碼到別的檔案

如果將PictureBox的Image存到IntroductionOBJ,存檔的時候會發生「在 GDI+ 中發生泛型錯誤」,原因是PictureBox透過Image.FromFile佔用了圖片檔,所以我們改以Byte[]型態儲存圖片。

Byte、Image跟Stream的轉換處理,我們另外抽離到新的類別檔案 PhotoHelper.cs introductionOBJ.photo = Image.FromFile(imagePath);

// PhotoHelper.cs

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace IT_Day01
{
    public class PhotoHelper
    {
        /// <summary>
        /// 圖片轉Bytes
        /// </summary>
        /// <param name="img"></param>
        /// <returns></returns>
        public static byte[] ImageToBytes(Image img)
        {
            MemoryStream memoryStream = new MemoryStream();
            img.Save(memoryStream, ImageFormat.Jpeg);

            return memoryStream.ToArray();
        }

        /// <summary>
        /// Bytes轉圖片
        /// </summary>
        /// <param name="imgBytes"></param>
        /// <returns></returns>
        public static Image bytesToImage(byte[] imgBytes)
        {
            MemoryStream memoryStream = new MemoryStream(imgBytes);

            return Image.FromStream(memoryStream);
        }

        public static MemoryStream readImageStreamFromFile(string imagePath)
        {
            MemoryStream memoryStream = new MemoryStream();

            Image imageFile = Image.FromFile(imagePath);
            imageFile.Save(memoryStream, ImageFormat.Jpeg);
            
            // 釋放資源,避免圖片檔案被占用
            imageFile.Dispose();

            return memoryStream;

        }
    }
}

設定圖片到PictureBox上面

photoBox.Image = PhotoHelper.bytesToImage(introductionOBJ.photo);

將PictureBox的圖片以Byte[]型態存到IntroductionOBJ

introductionOBJ.photo = PhotoHelper.ImageToBytes(photoBox.Image);

往明天邁進 OR 放棄? 準備為自我介紹畫下句點

這次的重構過程沒有用到太深的物件導向觀念(繼承、抽象、多型),主要用了「封裝」和「靜態方法」抽離UI和非UI的程式邏輯。

雖然對於一個小程式的程式碼量而言,重構的效益相對沒有像大系統一樣明顯,但是如果是一個真正的資訊系統,由於包含非常多模組,每個模組至少有上千行的程式碼,如果UI(View)、資料處理邏輯(Controller)、資料實體(Entity)全部混在一起,一旦程式碼出問題,需要付出非常大的維護成本。

在幫「自我介紹程式」畫下句點之前,我會分享一些自身程式教、學相關的非技術議題,以及這支專案的相關衍伸議題和應用。


上一篇
Project 1 - 自我介紹程式(6): 能否留下你的自介與相片 - 實作讀寫檔流程
下一篇
Project 1 - 自我介紹程式 : 對的問題 與 對的時間點 - 模仿→變化→改善→應用
系列文
當我遊走在程式的初學路上-從入門到放棄9
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言