付費AI工具,真的好可怕 - AI幫我重構

我使用的是 Google的 Gemini Pro [付費版],因為他會分析他的思路,還會幫程式碼做結論(重點摘要)

 

 

日前做了一些前端的工作(非UI、非美工)

但我對於前端實在苦手,不太行啊!

 

[工作規格與需求]:搜尋關鍵字,到GitLab找到相關的專案、倉庫(Repo)與超連結,以JSON格式傳回,並呈現在HTML畫面上

 

      運氣很好,GitLab有提供現成的API讓我呼叫,問題算是簡單

      只要你把傳回的JSON格式給AI工具,他就能幫你完成。

      …..後續要整合多台GitLab傳回的JSON,合併在一起,也不難( 對我來說 :-) 不難,因為我只是請AI幫我做好 )

 

後來需要連上另一套Source Code平台,名為 Gerrit Server。這套就麻煩了,

他傳回的JSON格式不標準,是一個物件。格式跟上面的也不一樣。…….這裡我就頭大了

 

=== 原本的程式碼 ===

可以看見功能幾乎一模一樣,但是細節稍微有點改變

這樣的程式碼,你交出去一定會被幹攪…….這時候,呼叫AI大神~~~

免費版的AI工具 - Copilot 就糟糕,因為他有字數限制。簡單的說,「免費的,最貴(服務不好)」

換成 "付費版" 就沒問題了!

我使用Google的 Gemini Pro。每次回答時,會先告訴你「他的思路」(如何解題?),最後還會跟我上課(教課)一樣,做重點整理與複習

這真的很厲害喔!這真的很厲害喔!

      <input type="text" id="keywordInput" placeholder=" ex: 'wifi'..." width="280px">    

      <button type="button"  id="searchButton">Search</button> 
           

                <div id="resultsContainer"  class="col-md-12 themed-grid-col">

                </div>

                <div id="resultsContainerGerrit"  class="col-md-12 themed-grid-col">

                </div>

      

    <script>

        const searchButton = document.getElementById('searchButton');

        const keywordInput = document.getElementById('keywordInput');

        const resultsContainer = document.getElementById('resultsContainer');

 

        searchButton.addEventListener('click', fetchProjects);

        keywordInput.addEventListener('keypress', (e) => {

            if (e.key === 'Enter') {

                e.preventDefault(); // 防止表單提交

                fetchProjects();

            }

        });

 

        async function fetchProjects() {

            const keyword = keywordInput.value.trim();

            resultsContainer.innerHTML = `<p class="loading"width="450px">....... Loading, Please wait ...... .</p >`;

 
            try {  // *** 避免CORS,透過HttpHandler (.ashx檔) 連過去 *****************               

                const proxyUrl = `/GitLabHandler.ashx?keyword=${encodeURIComponent(keyword)}`;
 
                const response = await fetch(proxyUrl, {

                    method: 'GET',

                    headers: {

                        'Accept': 'application/json'

                    }

                });

 

                const responseText = await response.text();


                if (!response.ok) {

                    let errorDetails = `HTTP 錯誤: ${response.status} - ${response.statusText}`;

                    try {

                        const errorJson = JSON.parse(responseText);

                        errorDetails += `. 後端訊息: ${errorJson.error || JSON.stringify(errorJson)}`;

                    } catch (e) {

                        errorDetails += `. 後端原始響應: ${responseText}`;

                    }

                    throw new Error(errorDetails);

                }

 

                let projects;

                try {

                    projects = JSON.parse(responseText);

                } catch (e) {

                    throw new Error(`無法解析後端響應為 JSON: ${e.message}. 原始響應: ${responseText}`);

                }


                if (!Array.isArray(projects)) {

                    throw new Error("接收到的數據不是一個有效的 JSON 數組。");

                }
                         
                // GitLab有API,傳回的JSON格式很標準
                displayResults(projects); // 將 projects (JSON)傳遞給 displayResults
 

            } catch (error) {

                console.error('搜尋失敗:', error);

            }

        }

 

        function displayResults(projects) {

            const resultsContainer = document.getElementById('resultsContainer'); // 確保這裡能正確獲取到容器

            resultsContainer.innerHTML = '';    /* 清空 舊結果 */
 

            if (projects.length === 0) {

                resultsContainer.innerHTML = '<p class="no-results">Not Found in GitLab Projects</p>';  /* 未找到符合關鍵字的專案。 */

            } else {

                const table = document.createElement('table');

                table.classList.add('table', 'table-striped', 'table-hover', 'table-bordered');

                table.id = "table1";

                const thead = document.createElement('thead');

                const tbody = document.createElement('tbody');

   
                thead.innerHTML = ` <tr>

                                                            <th align="left"><img src="GitLab_Logo_S1.jpg""></th>

                                                            <th>Project/Repo</th>

                                                            <th>Path</th>

                                                            <th>URL</th>

                                                            <th>Descr.</th>

                                                            <th>Last time</th>

                                                        </tr>`;

                table.appendChild(thead);

 

                projects.forEach(project => {

                    const row = document.createElement('tr');

                    // 確保這裡的屬性名稱與後端 JSON 完全匹配

                    row.innerHTML = `

                        <td>${project.SourceServer || '未知'}</td> <td>${project.name || 'N/A'}</td>

                        <td>${project.path_with_namespace || 'N/A'}</td>

                        <td class="project-url"><a href="${project.web_url}" target="_blank">&nbsp;...Hyperlink...&nbsp;</a></td>

                        <td><div class="project-description">${project.description || 'N/A'}</div></td>

                        <td>${project.last_activity_at ? new Date(project.last_activity_at).toLocaleString() : 'N/A'}</td>

                    `;

                    tbody.appendChild(row);

                });

                table.appendChild(tbody);

                resultsContainer.appendChild(table);

            } 

            /* ***完成後,繼續尋找 Gerrit並呈現結果 ***  */

            fetchProjectsGerrit();

        }

 

        /* -- Gerrit搜尋結果 -------- */

        async function fetchProjectsGerrit() {

            const keyword = keywordInput.value.trim();

            const resultsContainerGerrit = document.getElementById('resultsContainerGerrit');
 

            try {

                const proxyUrl = `/GerrithHandler.ashx?keyword=${encodeURIComponent(keyword)}`;

                const response = await fetch(proxyUrl, {

                    method: 'GET',

                    headers: {

                        'Accept': 'application/json'

                    }

                });

 

                if (!response.ok) {

                    // 同上

                }

 
                // Gerrit傳回的JSON格式,是一種物件。處理起來就不同。
                const data = await response.json(); // 這次接收的數據是包含 success, message 和 servers 的物件

                if (data.success) {

                    displayResultsGerrit(data.servers); // 將 servers 物件傳遞給 displayResults

                } else {

                    resultsContainerGerrit.innerHTML = `<p class="error">[Gerrit] Search Fail : ${data.message}</p>`;

                }
 

            } catch (error) {

                console.error('Gerrit搜尋失敗:', error);

            }

        }

 

        function displayResultsGerrit(servers) {

            resultsContainerGerrit.innerHTML = '';   // 清空舊結果

            const projects = [];   // ********

 

            for (const serverName in servers) {

                if (servers.hasOwnProperty(serverName)) {

                    const server = servers[serverName];

                    server.Projects.forEach(project => {

                        project.SourceServer = server.Name; // 添加來源伺服器信息

                        projects.push(project);

                    });

                }

            }

 

            if (projects.length === 0) {

                resultsContainerGerrit.innerHTML = '<p class="no-results">Not Found in Gerrit Projects</p>'; // 未找到符合關鍵字的專案

            } else {

                const table = document.createElement('table');

                table.classList.add('table', 'table-striped', 'table-hover', 'table-bordered');

                table.id = "table2"; 

                const thead = document.createElement('thead');

                const tbody = document.createElement('tbody');

                thead.innerHTML = `

                    <tr>

                        <th align="left""><img src="Gerrit_Logo_S1.jpg""></th>

                        <th>......同上,不贅述.... </th>

                    </tr>`;

                table.appendChild(thead);

 

                projects.forEach(project => {

                    const row = document.createElement('tr');

                    row.innerHTML = `

                        <td>${project.SourceServer || '未知'}</td>

                        <td>${project.name || 'N/A'}</td>

                        <td>......同上,不贅述.... </td>

                    `;

                    tbody.appendChild(row);

                });
 

                table.appendChild(tbody);

                resultsContainerGerrit.appendChild(table);

            }

        }

    </script>
    <!-- 註解:如果你有多台 Gitlab,或是多台 Gerrit Server,密碼(或token)、網址.....可以寫在 "設定檔" 裡面,統一管理與呼叫 -->
    <!--      .NET Framework 4.x版,不分Web Form或MVC "設定檔" ,名為 Web.Config。 --> 
    <!--      .NET Core 6.0起,設定檔名為 Program.cs。早期版本(.NET 5.0(含)以前)則是 Startup.cs -->

 

=== AI 重構以後 的 程式碼 === ……註:以下都是AI (Gemini Pro) 提供的內容,我只有加上一些註解

好的,這段程式碼確實有許多可以重構和簡化的空間。主要的重複點在於:

  1. 資料擷取邏輯 (fetch):兩個 fetch 函式 (fetchProjectsfetchProjectsGerrit) 的結構非常相似,都包含了設定 URL、發送請求、處理錯誤等步驟。
  2. 結果顯示邏輯 (display):兩個 display 函式 (displayResultsdisplayResultsGerrit) 在建立表格、標頭、以及遍歷資料產生表格內容的部份幾乎一模一樣。
  3. 資料結構的差異:這是兩組函式最主要的差別。GitLab 直接回傳一個專案陣列 [],而 Gerrit 回傳一個包含 servers 物件的結構 { "success": true, "servers": { ... } }

我們的目標是將相同的部分抽離出來,並將不同的部分作為參數傳遞

重構思路

  1. 建立一個主控函式 (handleSearch):取代原本的 fetchProjects,作為搜尋按鈕點擊後的唯一入口。
  2. 平行處理請求:使用 Promise.all 來同時對 GitLab 和 Gerrit 發送請求,而不是一個接一個地等待,這樣可以提升效率。
  3. 建立一個通用的資料擷取與顯示函式 (fetchAndDisplay):這個函式會接收一個設定物件,物件中包含了特定 Server 的所有資訊(如 API 路徑、顯示容器 ID、Logo 圖片、以及最重要的資料轉換函式)。
  4. 建立一個通用的結果顯示函式 (renderResults):這個函式只負責將標準化後的專案陣列渲染成 HTML 表格,不再關心資料是從哪裡來的。
  5. 使用設定檔管理差異:將每個 Server 的不同之處(API URL、Logo、資料處理方式等)都放在一個設定物件中,讓程式碼主體保持通用性。

Gemini Pro 重構後的程式碼

<div class="search-area">
    同上 ....不贅述....
</div>

<!-- 這裡有修正 -->
<div id="resultsGitLab" class="col-md-12 themed-grid-col"></div>
<div id="resultsGerrit" class="col-md-12 themed-grid-col"></div>

<script>
    // ------------------- DOM 元素宣告 (集中管理) -------------------
    const searchButton = document.getElementById('searchButton');
    const keywordInput = document.getElementById('keywordInput');

    // ------------------- 設定檔 (將差異點集中管理) -------------------
    // 為每個資料來源定義一個設定物件
    const sourceConfigs = [
        {
            name: 'GitLab',
            containerId: 'resultsGitLab',
            apiUrl: 'GitLabHandler.ashx',
            noResultsMessage: 'Not Found in GitLab Projects',
            // 資料轉換函式:將 API 回應轉換成標準的專案陣列
            transformData: (data) => {
                if (!Array.isArray(data)) {
                    throw new Error("GitLab response is not a valid JSON array.");
                }
                return data; // GitLab 的回應直接就是專案陣列,直接返回
            }
        },
        {
            name: 'Gerrit',
            containerId: 'resultsGerrit',
            apiUrl: '/GerritHandler.ashx',
            noResultsMessage: 'Not Found in Gerrit Projects',
            // 資料轉換函式:處理 Gerrit 特殊的 JSON 結構
            transformData: (data) => {
                if (!data.success) {
                    throw new Error(data.message || 'Gerrit search failed.');
                }
                
                // ***************************************  
                // 因為Gerrit Server傳回的JSON格式不標準,是一個物件,必須重新整理
                const projects = [];   
                for (const serverName in data.servers) {
                    if (Object.hasOwnProperty.call(data.servers, serverName)) {
                        const server = data.servers[serverName];
                        server.Projects.forEach(project => {
                            project.SourceServer = server.Name; // 注入來源伺服器名稱
                            projects.push(project);
                        });
                    }
                }
                return projects;
            }
        }
    ];

    // ------------------- 事件監聽 -------------------
    searchButton.addEventListener('click', handleSearch);
    keywordInput.addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            e.preventDefault();
            handleSearch();
        }
    });

    // ------------------- 主要邏輯函式 -------------------

    /**
     * 主控函式:當使用者觸發搜尋時執行
     */
    async function handleSearch() {
        const keyword = keywordInput.value.trim();
        if (!keyword) {
            // 清空所有結果並提示使用者輸入
            sourceConfigs.forEach(config => {
                const container = document.getElementById(config.containerId);
                container.innerHTML = `<p class="no-results">Please enter a keyword and click Search.</p>`;
            });
            return;
        }
        
        // ***************************************  
        // 使用 Promise.all 同時觸發所有來源的搜尋,提升效能
        await Promise.all(
            sourceConfigs.map(config => fetchAndDisplay(config, keyword))
        );
    }

    /**
     * 通用的資料擷取與顯示函式
     * @param {object} config - 來源的設定檔 (sourceConfigs 陣列中的一個元素)
     * @param {string} keyword - 搜尋關鍵字
     */
    async function fetchAndDisplay(config, keyword) {
        const container = document.getElementById(config.containerId);
        container.innerHTML = `<p class="loading">Searching ${config.name}... Please wait...</p>`;

        try {
            const proxyUrl = `${config.apiUrl}?keyword=${encodeURIComponent(keyword)}`;
            const response = await fetch(proxyUrl, {
                method: 'GET',
                headers: { 'Accept': 'application/json' }
            });

            if (!response.ok) {
                const errorText = await response.text();
                throw new Error(`HTTP Error: ${response.status} - ${response.statusText}. Details: ${errorText}`);
            }
            
            // ***************************************  
            const rawData = await response.json();
            
            // 使用設定檔中定義的 transformData 函式來處理特定來源的資料結構
            const projects = config.transformData(rawData);
            
            // 將標準化後的 projects 陣列交給通用的渲染函式
            renderResults(projects, config);
            // ***************************************  
            
        } catch (error) {
            console.error(`[${config.name}] Search failed:`, error);
            container.innerHTML = `<p class="error">[${config.name}] Search Failed: ${error.message}</p>`;
        }
    }

    /**
     * 通用的結果渲染函式
     * @param {Array} projects - 標準化後的專案物件陣列
     * @param {object} config - 來源的設定檔,用於獲取 Logo、容器 ID 等資訊
     */
    function renderResults(projects, config) {
        const container = document.getElementById(config.containerId);
        container.innerHTML = ''; // 清空舊結果或 "Loading..." 訊息

        if (projects.length === 0) {
            container.innerHTML = `<p class="no-results">${config.noResultsMessage}</p>`;
            return;
        }

        const table = document.createElement('table');
        table.classList.add('table', 'table-striped', 'table-hover', 'table-bordered');
        table.id = `table-${config.name.toLowerCase()}`; // e.g., table-gitlab

        // 表格標頭 (thead) - 使用設定檔中的 logo
        const thead = document.createElement('thead');
        thead.innerHTML = `
            <tr>
                <th align="left"><img src="${config.logo}" alt="${config.name} Logo"></th>
                <th>....同上,省略(不贅述).......
            </tr>`;

        // 表格內容 (tbody) - 這部分邏輯完全通用
        const tbody = document.createElement('tbody');
        projects.forEach(project => {
            const row = document.createElement('tr');
            // 欄位名稱 (name, path_with_namespace 等) 必須在轉換後保持一致
            row.innerHTML = `
                <td>${project.SourceServer || config.name}</td>
                <td>....同上,省略(不贅述).......
            `;
            tbody.appendChild(row);
        });

        table.appendChild(thead);
        table.appendChild(tbody);
        container.appendChild(table);
    }
</script>

Gemini Pro 重構後的優點

高內聚,低耦合 (High Cohesion, Low Coupling)

  • fetchAndDisplay 專注於網路請求和錯誤處理。
  • renderResults 專注於將資料渲染成畫面。
  • transformData 專注於處理不同來源的 JSON 資料格式。
  • 每個函式的職責都非常清晰。

可擴充性 (Scalability)

  • 如果未來要增加第三個來源(例如 Bitbucket),你幾乎不需要修改現有函式
  • 你只需要在 sourceConfigs 陣列中新增一個 Bitbucket 的設定物件,定義它的 apiUrlcontainerIdlogo,並實作它專屬的 transformData 函式即可。

可維護性 (Maintainability)

  • 所有重複的程式碼都被消除。如果你需要修改表格的 CSS class 或欄位順序,只需要在 renderResults 函式中修改一次。
  • 每個 Server 的 API 端點或資料結構若有變更,也只需要去 sourceConfigs 中對應的物件裡修改,不會影響到其他程式碼。

效能提升 (Performance):   ……註:學到一個新技巧…..前端我不熟悉,想不到有這東西!讚!

  • 使用 Promise.all 讓兩個網路請求併發執行,而不是串行等待。使用者可以更快地看到兩個來源的結果,尤其是在網路延遲較高時,這個差異會更明顯。

程式碼更簡潔易讀 (Readability)

  • 透過將差異點抽象化為設定檔,主體邏輯變得非常通用和乾淨,更容易理解整個搜尋流程。

 

……………………………………………………………………………………………………………………………………………………..

我也是一個有情有義的人,每次AI幫我忙,我都會謝謝他  :-) 

AI 的 人性化程度?說真的,你是不是我兄弟? https://dotblogs.com.tw/mis2000lab/2025/04/01/AI_Funny_Answer_20250401 

 

我想我老闆正在我後面,他非常火……. :-) 

 

 

 

我將思想傳授他人, 他人之所得,亦無損於我之所有;

猶如一人以我的燭火點燭,光亮與他同在,我卻不因此身處黑暗。----Thomas Jefferson

線上課程教學,遠距教學 (Web Form 約 51hr)  https://dotblogs.com.tw/mis2000lab/2016/02/01/aspnet_online_learning_distance_education_VS2015

線上課程教學,遠距教學 (ASP.NET MVC 約 140hr)  https://dotblogs.com.tw/mis2000lab/2018/08/14/ASPnet_MVC_Online_Learning_MIS2000Lab

 

寫信給我,不要私訊 --  mis2000lab (at) yahoo.com.tw  或  school (at) mis2000lab.net

 (1) 第一天 ASP.NET MVC5 完整影片(5.5小時 / .NET 4.x版)免費試聽。影片 https://youtu.be/9spaHik87-A 

 (2) 第一天 ASP.NET Core MVC 完整影片(3小時 / .NET Core 6.0~8.0)免費試聽。影片 https://youtu.be/TSmwpT-Bx4I 

[學員感言] mis2000lab課程評價 - ASP.NET MVC , WebForm  。 https://mis2000lab.medium.com/%E5%AD%B8%E5%93%A1%E6%84%9F%E8%A8%80-mis2000lab%E8%AA%B2%E7%A8%8B%E8%A9%95%E5%83%B9-asp-net-mvc-webform-77903ce9680b  


ASP.NET遠距教學、線上課程(Web Form + MVC)。 第一天課程, "完整" 試聽。 

.........   facebook社團   https://www.facebook.com/mis2000lab   ......................

.........  YouTube (ASP.NET) 線上教學影片  https://www.youtube.com/channel/UC6IPPf6tvsNG8zX3u1LddvA/

 

Blog文章 "附的範例" 無法下載,請看 https://dotblogs.com.tw/mis2000lab/2016/03/14/2008_2015_mis2000lab_sample_download

請看我們的「售後服務」範圍(嚴格認定)。

...................................................................................................................................................... 

ASP.NET MVC  => .NET Core MVC 線上教學  ...... 第一天課程 完整內容 "免費"讓您評估 / 試聽

[遠距教學、教學影片] ASP.NET (Web Form) 課程 上線了!MIS2000Lab.主講   事先錄好的影片,並非上課側錄!   觀看時,有如「一對一」面對面講課