2025年12月14日 星期日

Github Copilot 6 : 為程式碼自動加入註解

Github Copilot 6 : 為程式碼自動加入註解

適度的在程式碼中加入適當的註解,將會有助於日後專案維護的效率,雖然,有很多人不贊同在程式碼中加入過多的註解,不過,有些時候,適當的加入程式碼註解,將會有助於整體專案的理解。

這是一個 AI 影響性中心的臨床試驗平台的專案,需要將癌症治療與AI輔助分析進行整合,並且要收集不同週期的各種治療、抽血、問卷的數據,最重要的是,這些都是關於醫療方面的項目,對於像是我們這樣的程式設計人員,這些名稱其實對於我們並沒有受過這樣的專業訓練,因此,程式碼中充斥著這些醫學名詞,特別是在 Data Model 的定義上,首先,面對到的是如何取個適當的屬性名稱,最重要的是日後看到這個名稱,能夠與畫面上的欄位結合起來,畢竟,我這裡設計的畫面語系是中文文字。

例如底下這個類別 [臨床資料手術Node] 是一個要記錄臨床手術相關內容的類別,各個欄位的名稱來自於醫師提供的,並且要對應到畫面上

public class 臨床資料手術Node
{
    public VisitCodeModel VisitCode { get; set; }=new();
    public string SubjectNo { get; set; }
    public DateTime 手術日期 { get; set; }
    public string 術式 { get; set; }
    public string OPOutcome { get; set; }
    public string Ascites { get; set; }
    public string Uterus { get; set; }
    public string UterusSite { get; set; }
    public string UterusTumorNumber { get; set; }
    public string UterusTumorSize { get; set; }
    public string Cervix { get; set; }
    public string CervixSite { get; set; }
    public string CervixTumorNumber { get; set; }
    public string Endometrium { get; set; }
    public string Myometrium { get; set; }
    public string CulDeSac { get; set; }
    public string OvarianSurfaceRuptureOrNotRightOvary { get; set; }
    public string OvarianSurfaceRuptureOrNotLeftOvary { get; set; }
    public string LeftAdnexa { get; set; }
    public string LeftAdnexaTumorNumber { get; set; }
    public string LeftAdnexaTumorSize { get; set; }
    public string RightAdnexa { get; set; }
    public string RightAdnexaTumorNumber { get; set; }
    public string RightAdnexaTumorSize { get; set; }
    public string PelvicPeritonealCavity { get; set; }
    public string PelvicPeritonealCavityTumorSize { get; set; }
    public string ExtrapelvicPeritonealCavity { get; set; }
    public string ExtrapelvicPeritonealCavityOtherFinding { get; set; }
    public string OtherOrganInvolvementGrossLooking { get; set; }
    public string Optimal { get; set; }
    public string ResidualTumor { get; set; }

}

另外,若這個類別將會用於 Web API 上的使用,並且這些資訊要能夠讓使用 Swagger 的前端人員,知道這些欄位的意義與用法,此時,也需要適當的加入 xml 註解,如此,這些資訊可以在 Swagger 上呈現出來,因此,如何快速、有效與方便的建立起這些註解資訊,並且不會造成負擔,就是這篇文章想要表達的。

為 臨床資料手術Node 加入註解

在以往想要為一個類別內的不同屬性加入這些 XML 註解,這是一個相當具大與浪費時間的工作,也考驗著程式設計人員的耐心,很多人因為習慣或者偷懶,不會想要去做這些的工作,可是,一旦這些註解存在之後,除了可以在 Swagger 中讓前端人員快速理解傳遞參數的欄位意義,並且對於在日常維護工作上,也會有相當的益處,例如,當看到一段程式碼,如下圖

當看到這個欄位 [MenstrualStatus] 究竟是甚麼意思,這個欄位的型別為字串,當要看到該欄位值的時候,其為 0 或者 1 ,這又分別代表甚麼意思呢?

此時,若該資料模型欄位有使用 xml 註解,僅需要將游標移動到該欄位上,VS 2026 就會彈跳出一個小視窗,說明這是一個關於紀錄月經狀態,其數值將會有兩種狀態,分別是 0:停經 1:未停經;看到這些訊息是不是瞬間對於這段程式碼的內容更加清晰了呢?

  • 現在,打開 [臨床資料手術Node.cs] 這個檔案,這個檔案定義了 [臨床資料手術Node] 資料模型的類別,內容在上面已經有顯示出來。
  • 切換到 [Github Copilot 聊天] 視窗內
  • 在該視窗左上方點選 [建立新執行緒] 按鈕
  • 在下面聊天面板中,輸入這個 Prompt 指令,並且按下 Enter 鍵
加入xml註解
  • 雖然這個 Prompt 內容簡單又清爽,但是對於這個情境卻絲毫不會有任何影響
  • 等待 Github Copilot 完成生成之後,將會看到底下截圖 
  • 點選 [臨床資料手術Node.cs] 生成結果右上方的套用按鈕
  • 直接將生成結過套用到原始 [臨床資料手術Node.cs] 檔案中
  • 這是尚未套用前的畫面 
  • [臨床資料手術Node.cs] 視窗內,按下多次 [Tab] 按鍵,完成套用程序
  • 這是套用之後的畫面 
  • 可以看到,原本沒有任何註解的程式碼,已經被自動加入了完整的 xml 註解

 




Github Copilot 5 : 程式碼自動完成輔助

 

Github Copilot 5 : 程式碼自動完成輔助

在這一篇文章,將會來分享我在使用 Github Copilot 的自動程式碼輔助生成的經驗。這是一個關於醫院的運動員儀表板的專案開發,在這個專案中,我會示範如何利用 Github Copilot 來協助完成一些程式碼的撰寫工作,並且展示這些功能如何提升開發效率。

該系統將會透過 Excel 檔案來提供身體組成與其他相關數據,其中有些數據則需要從不同的 Sheet 內的不同 cell 來進行計算而得到,其公式如下圖所示:

這些數據將會透過 Blazor 頁面來進行顯示,並且這類型的專案通常需要撰寫大量的程式碼來處理資料的讀取、計算以及顯示等工作,一旦在程式碼中的公式撰寫錯誤,或者指定錯誤 cell 來源,將會造成整體呈現的數據失真。

另外,一旦需要進行功能的擴充,或者是修改計算公式時,也需要花費大量的時間來進行程式碼的調整與測試。

這個專案採用了 MVP 的方式來進行開發,所以,能夠快速與準確的撰寫程式碼是非常重要的任務。

透過 Github Copilot 的協助,我能夠更快速地完成這些任務,並且減少了手動撰寫程式碼的時間。

以下是我在這個專案中使用 Github Copilot 的一些範例:

對於讀取 Excel 檔案內容的部分,這裡採用的是 Syncfusion.XlsIO 套件來進行 Excel 檔案的讀取工作,以下是我使用 Copilot 來協助完成讀取 Excel 檔案的程式碼:

IWorksheet worksheetCT身體組成 = workbook
            .Worksheets[MagicObjectHelper.CT身體組成原始擋20241212WorkSheetName];

根據螢幕截圖,對於 健康肌肉比 的計算公式,為 S13/(I13+N13+S13)

首先,將這段註解寫到程式碼中

// 健康肌肉比 的計算公式,為 S13/(I13+N13+S13)

接著,使用 Copilot 來協助完成程式碼的撰寫

在該註解寫完之後,按下 Enter 按鍵,Copilot 會自動建議以下的程式碼:

var MuscleQuality肌肉品質 = healthManagementModel.BodyMuscleQuality肌肉品質Model;

接著,再度按下 Enter ,Copilot 會自動建議以下的程式碼:

double S13 = worksheetCT身體組成.Range["S13"].Value.ToDouble();

在這裡將會讀取 Sheet 名稱為 MagicObjectHelper.CT身體組成原始擋20241212WorkSheetName ,其中第 S13 cell 的值,並且轉換成 double 型別。接著儲存到 S13 變數內

接著,再度按下 Enter ,Copilot 會自動建議以下的程式碼:

double I13 = worksheetCT身體組成.Range["I13"].Value.ToDouble();

接著,再度按下 Enter ,Copilot 會自動建議以下的程式碼:

double N13 = worksheetCT身體組成.Range["N13"].Value.ToDouble();

現在將會是重點,此時,再度按下 Enter ,Copilot 會自動建議如圖程式碼:

!

從這裡可看到所建議的程式碼雖然已經產生,但是,還是會有些錯誤,不過,關於公式的部分是正確的,接著,只需要將程式碼稍作修改即可,最終的程式碼如下所示:

var 健康肌肉比 = (S13 / (I13 + N13 + S13)).ToString("F2");

在這裡簡短的操作示範中看到,因為最初有加入計算公式的註解,讓 Copilot 能夠更精準的理解我要完成的任務,並且提供相對應的程式碼建議,雖然最後還是需要進行一些修改,但是,大幅減少了撰寫程式碼的時間。

當然,日後若公式有所變更,這裡也僅需要修改註解中的公式,然後再公式的程式碼進行刪除,接著,在按下 Enter 鍵,Copilot 就會再次根據新的註解內容來提供新的程式碼建議,這樣的方式能夠大幅提升程式碼撰寫的效率,並且減少錯誤發生的機率。




Github Copilot 1 : 透過ChatGPT將畫面直接產生出 Blazor 頁面

Github Copilot 1 : 透過ChatGPT將畫面直接產生出 Blazor 頁面

前言

最近這段時間,都在從事於 FHIR & AI 相關的醫療專案開發,在這一年中,透過AI的輔助,讓許多系統的開發可以順利、快速、有效地進行,而在這些AI工具中,Github Copilot 無疑是我最常使用的工具之一。而面對身旁的同事們,常常會問到:「你是怎麼使用 Github Copilot 來加速開發的?」、「Github Copilot 究竟能幫助我們多少?」等問題。為了讓大家更了解這個工具的強大功能,所以,我將會透過一系列的文章,逐步來分享我在這些應用的經驗。這篇文章將分享我如何利用 Github Copilot 來快速產生 Blazor 頁面。

首先,為了要能夠快速協助醫生將其需求,在最短的時間內,採用 MVP 的方式開發出來,透過不斷的迭帶,來讓醫生能夠在最短的時間內,看到系統的雛形,並且能夠快速地進行調整與修改。

所以,一開始在雙方進行需求溝通的過程中,將採用另外一種模式,讓醫師使用簡報檔案,來描述其需求畫面,在簡報畫面中,加入相關註解與說明,而且版面配置也請醫師儘可能的透過簡報來呈現出來。

依據以往的開發方式,將會需要採用類似 Figma 這類工具,並且將這些簡報檔案,設計出來,並且進行切板與接下來轉換成 Blazor 頁面。如此,這樣又多出了一個過程,衍伸出來的也包含了討論與修正的時間,而且,對於網頁設計師而言,收到這些視覺設計稿之後,還需要進行切版,並且轉換成 Blazor 頁面,這些都會增加開發的時間。

以往,我可能會將該畫面貼到 ChatGPT 這類工具,並且請它協助我產生出 HTML 與 CSS 的程式碼,不過,這樣的方式,需要多次的透過剪貼簿來進行將內容轉移。這樣的操作體驗似乎也是不太方便與流暢。

在當時我對於 Github Copilot 這樣的工具也不太孰悉,不過,當下我便想到,或許可以嘗試看看,是否能夠直接將這些簡報畫面,貼到 Visual Studio Code 的編輯器中,並且請 Github Copilot 協助我來產生出 Blazor 頁面的程式碼。因此,立馬進行與驗證這樣的想法。

底下為 AI 臨床試驗平台的儀表板畫面,這是醫師直接使用 PowerPoint 繪製出來的畫面。

首先,我這裡將這個畫面貼到 OpenAI 的 ChatGPT 網頁中,並且請它協助我產生出 Blazor 頁面的程式碼,底下為我給 ChatGPT 的指令內容:

因為這個時間點,GPT的最新版本為 5.1 版本,當時僅有 4.1 版,因此,為了要能夠重現當時狀況,這裡還是採用 4.1 的版本

將這個畫面轉成 html & css

建立 Blazor 專案

  • 這裡建立一個 Blazor Server 專案
  • 在 [Components] > [Pages] 目錄下,建立一個 [Sample1.razor] & [Sample1.razor.css] 檔案 
  • 將產生出來的程式碼,貼到 Sample1.razor 頁面中。
  • 這是完成的程式碼
@page "/Sample1"

    <style>
        body {
          background: #ececec;
          margin: 0;
          font-family: "Segoe UI", Arial, "Microsoft JhengHei", "微軟正黑體", sans-serif;
        }
        .container {
          margin: 20px auto;
          padding: 24px;
          background: #fff;
          border-radius: 10px;
          max-width: 1150px;
          box-shadow: 0 0 10px #bbb;
        }
        .title {
          font-size: 2em;
          font-weight: 600;
          letter-spacing: 2px;
          margin-bottom: 18px;
        }
        .title span {
          font-weight: 900;
          font-size: 1.2em;
          letter-spacing: 4px;
        }
        .flex-row {
          display: flex;
          gap: 16px;
        }
        .section {
          border: 2px solid #d2d2d2;
          border-radius: 8px;
          padding: 10px 12px 16px 12px;
          background: #f6f8fa;
          flex: 1;
          min-width: 340px;
          margin-bottom: 16px;
        }
        .subject-no {
          background: #fff;
          border: 2px solid #d2d2d2;
          border-radius: 8px;
          margin-bottom: 8px;
          padding: 6px 12px;
          font-size: 1.15em;
          font-weight: 600;
          color: #1a1a1a;
          letter-spacing: 1px;
        }
        .subject-no .highlight {
          color: #e6001f;
          font-weight: 900;
          margin-left: 10px;
          letter-spacing: 2px;
        }
        .sub-label {
          font-size: 0.95em;
          color: #428bca;
          font-weight: 400;
          margin-left: 7px;
          letter-spacing: 1px;
        }
        .table-block {
          width: 100%;
          border-collapse: collapse;
          margin-top: 6px;
        }
        .table-block th,
        .table-block td {
          border: 1px solid #bbb;
          padding: 6px 8px;
          font-size: 1em;
          text-align: left;
          background: #fff;
        }
        .table-block th {
          background: #ececec;
          font-weight: 700;
        }
        .table-block .sub-field {
          color: #9e005d;
          font-weight: 600;
          font-size: 0.92em;
        }
        .table-block .calculation {
          color: #6a7287;
          font-size: 0.90em;
          font-style: italic;
        }
        .section-header {
          font-size: 1.12em;
          font-weight: 700;
          letter-spacing: 1px;
          margin-bottom: 6px;
          background: #fff3e0;
          padding: 4px 8px;
          border-radius: 4px;
        }
        .ct-section {
          flex: 0 0 350px;
          display: flex;
          flex-direction: column;
          align-items: center;
          background: #fff;
          border: 2px solid #c876da;
          border-radius: 10px;
          padding: 0 0 18px 0;
          margin-left: 10px;
          min-width: 330px;
          max-width: 370px;
          margin-bottom: 16px;
        }
        .ct-title {
          background: linear-gradient(90deg, #b30086 0%, #b300b3 100%);
          color: #fff;
          font-size: 2em;
          font-weight: 700;
          text-align: center;
          padding: 12px 0;
          width: 100%;
          border-radius: 8px 8px 0 0;
          letter-spacing: 1.5px;
          margin-bottom: 0;
        }
        .ct-image {
          width: 92%;
          margin: 20px 0 16px 0;
          border-radius: 6px;
          background: #111;
          display: flex;
          align-items: center;
          justify-content: center;
          min-height: 190px;
          position: relative;
          overflow: hidden;
        }
        .ct-image img {
          width: 100%;
          border-radius: 6px;
          object-fit: contain;
          background: #222;
        }
        .ct-image .l3-label {
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -20%);
          color: #e386e5;
          font-size: 2.1em;
          font-weight: 700;
          letter-spacing: 2px;
          pointer-events: none;
          text-shadow: 0 0 10px #fff, 0 0 12px #c44;
        }
        .ct-report {
          width: 94%;
          margin: 0 auto;
          border: 2px solid #b300b3;
          border-radius: 0 0 8px 8px;
          background: #fff;
          font-size: 1.5em;
          text-align: center;
          color: #1a1a1a;
          padding: 8px 0;
          font-weight: 600;
          margin-top: 6px;
          margin-bottom: 0;
        }
        /* 底部按鈕列 */
        .footer-buttons {
          margin-top: 18px;
          display: flex;
          gap: 16px;
          justify-content: center;
        }
        .footer-buttons .btn {
          padding: 16px 32px;
          font-size: 1.12em;
          border-radius: 18px;
          border: none;
          font-weight: bold;
          color: #fff;
          cursor: pointer;
          min-width: 130px;
          box-shadow: 0 2px 6px #aaa;
          letter-spacing: 2px;
          transition: background 0.15s;
        }
        .btn-clinic { background: #2ecc71; }
        .btn-img    { background: #b55ca6; }
        .btn-blood  { background: #26a5dd; }
        .btn-ctcae  { background: #69c830; }
        .btn-ctcae strong { font-weight: 900; letter-spacing: 1.5px; }
        .btn-form   { background: #268dce; }
        .btn-follow { background: #94259b; }
        .footer-buttons .btn:hover { filter: brightness(0.95); }
    </style>
  
    <div class="container">
        <div class="title">
            Basic clinical presentation <span>臨床資訊</span>
        </div>
        <div class="flex-row">
            <!-- 左側:臨床資料 -->
            <div class="section" style="flex:1.1;">
                <div class="subject-no">
                    病人編號 Subject No
                    <span class="highlight">NCKUH-E001</span>
                    <span class="sub-label">(E代表內膜癌, O代表卵巢癌)</span>
                </div>
                <table class="table-block">
                    <tr>
                        <th style="width:120px;">臨床資訊, 癌別<br>EC or OC(自填)</th>
                        <td></td>
                    </tr>
                    <tr>
                        <th>年齡 (Age)</th>
                        <td class="sub-field">數值型<br>20歲~80歲</td>
                    </tr>
                    <tr>
                        <th>月經狀態</th>
                        <td class="sub-field">數值型<br>0停經、1未停經</td>
                    </tr>
                    <tr>
                        <th>身高(Height) cm</th>
                        <td class="sub-field">數值型<br>140cm~180cm</td>
                    </tr>
                    <tr>
                        <th>體重(BW) Kg</th>
                        <td class="sub-field">數值型<br>30kg~120kg</td>
                    </tr>
                    <tr>
                        <th>BMI (Kg/m²)</th>
                        <td class="calculation">
                            計算公式說明如下:<br>
                            體重(公斤) ÷ 身高(公尺) ÷ 身高(公尺)
                        </td>
                    </tr>
                    <tr>
                        <th>體表面積(BSA) m²</th>
                        <td class="calculation">
                            計算公式說明如下:<br>
                            體重(公斤) x 身高(公分) ÷ 3600,開根號
                        </td>
                    </tr>
                    <tr>
                        <th>腰圍(AC) cm</th>
                        <td class="sub-field">自填</td>
                    </tr>
                    <tr>
                        <th>日常體能狀態(PS)</th>
                        <td class="sub-field">數值型<br>0, 1, 2</td>
                    </tr>
                </table>
            </div>
            <!-- 中間:癌症狀態 -->
            <div class="section" style="flex:0.9;">
                <div class="section-header">癌症狀態 癌別 卵巢/子宮</div>
                <table class="table-block">
                    <tr>
                        <th style="width:180px;">癌症分期(2023 FIGO)</th>
                        <td class="sub-field">拉選式</td>
                    </tr>
                    <tr>
                        <th>AJCC c stage</th>
                        <td class="sub-field">拉選式</td>
                    </tr>
                    <tr>
                        <th>AJCC p stage</th>
                        <td class="sub-field">拉選式</td>
                    </tr>
                    <tr>
                        <th>組織型態</th>
                        <td class="sub-field">拉選式</td>
                    </tr>
                    <tr>
                        <th>MMR protein</th>
                        <td class="sub-field">拉選式</td>
                    </tr>
                    <tr>
                        <th>p53</th>
                        <td class="sub-field">拉選式</td>
                    </tr>
                    <tr>
                        <th>Hormon status</th>
                        <td class="sub-field">拉選式</td>
                    </tr>
                </table>
            </div>
            <!-- 右側:CT 影像區 -->
            <div class="ct-section">
                <div class="ct-title">CT Image</div>
                <div class="ct-image">
                    <!-- 替換成你自己的 CT 影像路徑 -->
                    <img src="https://cdn.openai.com/chatgpt/image_example_l3_ct.png" alt="CT L3" />
                    <div class="l3-label">L3</div>
                </div>
                <div class="ct-report">Report</div>
            </div>
        </div>
        <!-- 底部按鈕列 -->
        <div class="footer-buttons">
            <button class="btn btn-clinic">臨床資料</button>
            <button class="btn btn-img">影像資料</button>
            <button class="btn btn-blood">抽血資料</button>
            <button class="btn btn-ctcae"><strong>CTCAE 5.0</strong></button>
            <button class="btn btn-form">問卷</button>
            <button class="btn btn-follow">追蹤資料</button>
        </div>
    </div>

這樣的處理過程,似乎能夠讓一個圖片畫面快速的轉換成為 Blazor 的頁面,但是,似乎還是有些不太流暢的地方,因為當要進行修正或調整的時候,還是需要反覆地將修改後的程式碼貼回到 ChatGPT 內,並將生成後的結果再度貼回到 VS 2026 內,重新執行來觀看結果,若此時將 HTML & CCS 分離出來,將 CSS 內容儲存到 [Sample1.razor.css] 內,剪貼的過程會更加複雜與繁瑣,接下來,我將會嘗試看看,是否能夠直接在 Visual Studio 2026 編輯器中,直接貼上這個圖片,並且請 Github Copilot 協助我來產生出 Blazor 頁面的程式碼。