隨著AI技術的進步,AI輔助工具正逐漸改變程式設計的樣貌。以往仰賴人工撰寫的程式碼註解,現在可以透過AI工具快速生成,甚至能根據程式碼邏輯自動產生更具描述性的註解。模組化程式設計在AI工具的輔助下,能更有效率地分解複雜任務,並確保程式碼結構清晰易懂。在專案啟動階段,AI工具可以協助設定專案的基礎架構,讓開發者能更專注於核心功能的開發。自動填充功能則能減少重複性的程式碼撰寫工作,例如設定CSS斷點或生成樣板程式碼。此外,AI工具還能協助程式碼重構,提升程式碼的可讀性和可維護性,例如簡化複雜的條件陳述式或重新命名變數和函式。物件導向程式設計的複雜概念,如封裝、繼承和多型,也能透過AI工具的輔助更容易理解和應用。最後,在資料函式庫與資料處理方面,AI工具也能提供建議,協助開發者選擇合適的資料函式庫和設計高效的資料架構。
程式設計的新趨勢:AI輔助工具的應用
有趣的是,由於AI技術的發展,撰寫註解(comments)似乎變得有些過時。如果程式碼讓你百思不得其解,你可以直接詢問AI工具來幫你解析,不是嗎?這的確是事實。
另一件值得注意的事情是,撰寫提示(prompts)正逐漸成為新的註解方式。畢竟,大多數工具都將提示包含在註解行中。
當然,如果你想為程式碼新增註解,可以使用一個簡單的提示,例如:
Prompt:根據最佳程式設計實踐,新增清晰的註解。
是否新增註解取決於你,沒有放諸四海皆準的規則。這一切都取決於什麼對你和你的團隊最有效。但有一點是肯定的——使用AI輔助工具可以輕鬆地為你的程式碼新增註解。
模組化程式設計
模組化程式設計是高效軟體開發的根本。透過模組化程式設計,程式碼更加有組織、更容易理解和維護。它也使得與其他開發者合作變得更加順暢,因為大家不會互相干擾。此外,模組是可以重複使用的;重複使用可以節省大量時間,保持一致性,並減少錯誤的可能性。
內容解密:
模組化程式設計的價值同樣適用於與AI輔助工具的協作。它們無法從一個簡單的提示中產生一個複雜的應用程式。它們不是魔法師。但是,如果你將任務分解成清晰、具體的部分,這些工具就會發揮出色。否則,你可能會得到一團糟的程式碼,完全偏離軌道。
根據Private Market Labs的共同創始人兼首席產品官Titus Capilnean的說法:
在我開始使用AI工具後,我可以專注於解決問題和我的方法,而不是實際編寫的程式碼的瑣碎細節。當我遇到技術問題時,我首先將其分解成更小的部分,其中輸入和輸出是清晰的。這樣做的原因是,我使用的AI工具的上下文視窗可能不足以一次性提出好的解決方案。我發現,如果我要求模型提供使用簡單輸入、執行單一任務並提供可驗證輸出的程式碼,那麼除錯和構建會更容易。
開始一個專案
在開始一個程式設計專案時,你可能會遇到「冷啟動問題」或「空白頁面問題」。
在這種情況下,你會盯著一個空白的螢幕,沒有程式碼、沒有資料,甚至沒有明確的前進方向。毫無疑問,這聽起來很令人感到不知所措。第一個大難題是選擇專案的架構、設計模式和使用的技術。這些決策非常重要,因為你將在長期內與它們共存,所以你希望盡可能地正確做出這些決定。
別忘了人員因素。讓你的團隊站在同一頁上,找出如何有效地溝通,並從頭開始建立工作流程——這些可能與技術一樣具有挑戰性。這不僅僅是關於敲下那些第一行程式碼。這是關於為接下來的一切奠定堅實的基礎。解決這個階段需要聰明的規劃、技術知識和良好的團隊合作。
AI輔助程式設計工具可以提供很大的幫助。你可以使用它們來設定應用程式的基本腳手架。你將找出一個符合你願景的自定義起點。這些工具讓你免於初始設定的瑣碎工作,讓你可以直接跳入專案中更有趣的部分。
你可以提出一個特定的任務,然後提示大語言模型(LLM)生成樣板程式碼或起始程式碼。
例如:
Prompt:為一個聚合使用者從多個平台上的社交媒體動態匯聚到單一儀錶板介面的網頁應用程式生成起始程式碼。我建議使用什麼語言和框架?總體檔案結構應該如何?
ChatGPT首先建議使用React來建立一個流暢的單頁應用程式(SPA)。它還建議使用Redux來維護強型別和可重複使用的元件。然後,它建議使用Node.js來執行應用程式,並使用Express.js作為API端點。它還建議使用Passport.js進行登入,以及MongoDB和Mongoose進行資料函式庫和模型建立。至於檔案結構,如圖8-3所示。
自動填充
你知道當你沉浸在程式設計中,設定時間單位常數時,就是一行接一行重複的工作嗎?這就是GitHub Copilot發揮作用的地方。你可以讓它提供自動填充。
舉個例子,假設你正在建立一個回應式應用程式。這意味著你需要為CSS-in-JS styled-components函式庫設定斷點常數。
你可以先寫下:
breakpoints = {
'xs': '320px', # Extra small devices
然後在內聯聊天中,你可以使用這個提示:
Prompt:為其他螢幕尺寸建立常數。
圖8-4顯示了結果。它提供了其他螢幕尺寸和具有類別似風格的變數。
內容解密:
這段程式碼定義了一個名為breakpoints的字典,用於儲存不同螢幕尺寸的斷點值。這樣可以方便地在應用程式中使用這些值,並且可以輕鬆地進行修改和維護。透過使用自動填充功能,可以快速地完成這項工作,而不需要手動輸入每個斷點值。
Copilot可以進一步幫助自動填充,方法是檢視專案中的開啟檔案。
根據Capilnean的說法:
AI工具還透過允許我在將程式碼片段傳送到編譯器或佈署雲端函式之前檢查其正確性來提高我的生產力。我只需將部分程式碼貼到ChatGPT視窗中,然後詢問它是否正確,假設我已經初始化/提供了該程式碼片段正確執行的上下文(例如,匯入、變數、常數、自定義函式等)。這樣可以讓我產生執行良好且更快的解決方案。
重構程式碼:讓程式碼更清晰、更易維護
重構是軟體開發中的一個重要過程,旨在改善程式碼的結構、可讀性和可維護性,而不改變其外部行為。就像為程式碼進行大掃除一樣,重構可以讓程式碼變得更整潔、更有條理。
為什麼重構很重要?
重構可以保持程式碼函式庫的健康狀態,減少雜亂無章的程式碼,讓開發者更容易理解程式碼的運作。這不僅可以減少開發過程中的挫折感,還可以提高程式碼的效率和降低錯誤率。
AI輔助工具在重構中的作用
AI輔助工具可以大大簡化重構的過程。透過提供適當的提示,這些工具可以幫助開發者快速地簡化複雜的程式碼,重新組織程式碼結構,使其變得更加優雅和易於維護。
簡化複雜程式碼
複雜難懂的「忍者程式碼」雖然可能看起來很巧妙,但長期來看會增加維護成本。AI 工具可以幫助我們將其重構為更清晰、更易於理解的形式。
範例:簡化費波那契數計算函式
忍者程式碼 (Ninja Code):
// 一個立即執行的函式,用於計算第 n 個費波那契數,但可讀性較差
console.log((function(n, a = 0, b = 1) { while (--n) [a, b] = [b, a + b]; return a; })(10));
重構後的程式碼: 利用 AI 輔助工具,可以將其簡化為:
function calculateFibonacci(n) {
// 初始化費波那契數列的前兩個數字
let a = 0;
let b = 1;
// 迴圈 n-1 次來計算第 n 個費波那契數
for (let i = 0; i < n - 1; i++) {
[a, b] = [b, a + b]; // 交換變數並計算下一個數
}
return a;
}
console.log(calculateFibonacci(10));
費波那契數計算邏輯活動圖
圖表描述 (Alt Text): 此活動圖展示了計算費波那契數的標準流程,從初始化變數 a 和 b 開始,透過迴圈迭代更新這兩個變數的值,最終返回計算結果。
@startuml
!theme _none_
skinparam dpi auto
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam minClassWidth 100
skinparam defaultFontSize 14
title 費波那契數計算邏輯
start
:初始化 a = 0, b = 1;
:for 迴圈 (i 從 0 到 n-2);
:[a, b] = [b, a + b];
:end for;
:返回 a;
stop
@enduml
提取方法(Extract Method)
提取方法是一種重構技術,透過將長方法或函式中的一部分程式碼提取到一個新的方法中,使程式碼變得更加模組化和易於理解。
示例提示:
- 在功能性程式語言中提取方法時,有哪些常見的陷阱需要避免?
- 我附上了一段C#程式碼,你能否建議哪些部分適合使用提取方法重構?
- 你能否比較原始函式和重構後的版本,哪個更有效率?
分解條件陳述式 (Decomposing Conditionals)
將複雜的 if-then-else 陳述式分解成更小、更易於管理的部分,可以使程式碼更加清晰。
範例:重構使用者權限檢查
重構前:
if (user.isActive() && user.hasSubscription() && !user.isBlocked()) {
// 執行操作
}
重構後:
// 將複雜的條件判斷封裝到一個獨立的方法中
if (canUserAccessContent(user)) {
// 執行操作
}
// ...
boolean canUserAccessContent(User user) {
return user.isActive() && user.hasSubscription() && !user.isBlocked();
}
使用者權限檢查序列圖
圖表描述 (Alt Text): 此序列圖展示了 canUserAccessContent 方法的互動過程。主程式呼叫此方法,方法內部依序檢查使用者的 isActive、hasSubscription 和 isBlocked 狀態,並將最終的布林結果返回。
@startuml
!theme _none_
skinparam dpi auto
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam minClassWidth 100
skinparam defaultFontSize 14
title 使用者權限檢查流程
participant "MainLogic" as Main
participant "User Object" as User
Main -> User: canUserAccessContent(user)
activate User
User -> User: isActive()
User -> User: hasSubscription()
User -> User: isBlocked()
User --> Main: return boolean
deactivate User
@enduml
重新命名(Renaming)
重新命名函式、變數和類別是改善程式碼可讀性和可維護性的簡單而有效的方法。良好的命名可以讓其他開發者(或未來的自己)更容易理解程式碼的意圖。
重構程式碼:提升可讀性與可維護性的關鍵步驟
重構程式碼是軟體開發過程中不可或缺的一環,旨在改善程式碼的結構、可讀性和可維護性,而不改變其外部行為。本文將探討重構的幾個重要導向,包括命名、移除死碼、以及函式的最佳實踐。
命名的重要性
良好的命名可以大幅提升程式碼的可讀性和理解性。當變數或函式的名稱能夠準確反映其功能和用途時,其他開發者(甚至是原作者在未來)能夠更容易地理解程式碼的意圖。
重構命名的技巧
重構命名是常見的需求,尤其是在程式碼隨著時間演變,原有的名稱不再準確描述其功能時。例如,一個最初名為 processData 的方法可能變得更專門化,將其重新命名為 filterInvalidEntries 可以立即闡明其功能。
撰寫重新命名的提示相當簡單:
- 請求LLM建議SQL指令碼中代表資料函式庫使用者總數的變數名稱。
- 請求LLM檢視JavaScript程式碼片段中的變數和函式名稱,並提出改進建議。
- 請求LLM為Java類別中的名稱提出更好的建議,以提高畫質晰度。
然而,使用像Copilot這樣的工具時需要謹慎,因為更改名稱可能會破壞仍在使用舊名稱的程式碼部分。
移除死碼
顧名思義,死碼是指未被使用的程式碼,通常是被遺忘的功能或因更新而變得冗餘的部分。清理這些死碼可以使專案更加整潔和易於管理,也減少新加入者對無用程式碼的困惑。
移除死碼的提示
- 請求LLM幫助識別JavaScript片段中的潛在死碼。
- 請求LLM指出Python專案中未使用或冗餘的程式碼。
- 請求LLM檢視SQL程式,並確認是否可以安全刪除某些程式。
然而,使用LLM來識別死碼存在風險,因為有時看似無用的程式碼可能在特殊情況下很重要。此外,移除一段程式碼可能會影響依賴它的其他部分,尤其是當它涉及複雜邏輯或設定時。
替代工具
在處理死碼時,替代工具可能是更好的選擇。例如:
- Linter:如JavaScript的ESLint、Python的Pylint和Ruby的RuboCop,可以檢查語法錯誤、潛在bug和無用的程式碼。
- 靜態程式碼分析工具:如SonarQube、Code Climate和Coverity,可以在不執行程式碼的情況下檢查複雜模式,包括死碼。
函式的最佳實踐
函式是程式設計的基本組成部分,在任何軟體程式中都扮演著重要角色。良好的函式設計可以保持程式碼的整潔和易讀性,並使複雜軟體的管理變得更加容易。
編寫有效函式的
單一職責原則:函式應該專注於一項任務,使其更容易理解、測試和修復。
清晰命名:給函式一個清楚描述其功能的名稱,如
calculateTotalPrice,以提高可讀性。保持簡潔:理想情況下,函式應該足夠短小,以便在不滾動螢幕的情況下完整檢視。簡短的函式更容易管理和除錯。
引數和傳回值:使用引數作為輸入,傳回值作為輸出,使函式具有可預測性和獨立性。
一致性:遵循語言或專案的編碼規範和風格,保持程式碼的一致性和可讀性。
示例提示
請求LLM撰寫一個Python函式
calculate_area,接受兩個整數引數(長度和寬度),並傳回矩形的面積。包含docstring說明函式目的,並確保函式對非整數輸入引發TypeError。請求LLM建立一個JavaScript函式
filterAndTransform,接受一個物件陣列(包含name和age屬性),傳回一個新的陣列,包含年齡18歲或以上的人的名字,並轉換為大寫。包含註解說明邏輯。請求LLM建立一個C++函式
efficientSort,對整數陣列進行升序排序。最佳化時間複雜度,並在函式內包含註解說明排序演算法的選擇及其時間複雜度。請求LLM生成一個Java函式
safeDivide,接受兩個雙精確度浮點數引數(分子和分母),並傳回它們的商。如果分母為零,則傳回自定義錯誤訊息。包含Javadoc註解說明函式及其錯誤處理機制。
內容解密:
這些提示展示瞭如何利用LLM來輔助編寫符合最佳實踐的函式。透過明確指定函式的輸入、輸出、錯誤處理和效能要求,可以生成高品質且易於維護的程式碼。正確使用LLM可以大幅提升開發效率和程式碼品質。
物件導向程式設計:軟體開發的核心思維
物件導向程式設計(Object-Oriented Programming, OOP)是一種使用「物件」來代表資料和方法的程式設計方式。想像一下,你正在開發一系列小型、自成體系的容器,每個容器都具備自己的工具和資訊。這些容器,被稱為類別(classes),就像是創造不同物件的藍圖。類別定義了物件的結構和行為,類別似於範本。然後,從這個類別中,你可以創造出個別的物件,每個物件都有自己的特定細節,但遵循相同的基礎結構。
物件導向程式設計的核心概念
深入探索物件導向程式設計的世界,感覺就像步入一個充滿抽象(abstraction)、繼承(inheritance)、封裝(encapsulation)和多型(polymorphism)等複雜概念的迷宮。這些概念可能令人感到陌生和難以理解。
封裝 (Encapsulation)
封裝是將資料(屬性)和操作資料的方法(行為)捆綁在一起的機制,並隱藏物件的內部狀態,僅透過公開的介面進行互動。
Python 範例:
class BankAccount:
# 初始化方法,設定帳戶屬性
def __init__(self, account_number, balance=0):
# 使用雙下劃線將屬性設為私有,實現封裝
self.__account_number = account_number
self.__balance = balance
# 存款方法
def deposit(self, amount):
if amount > 0:
self.__balance += amount
print(f"存入 {amount}。新餘額為 {self.__balance}")
else:
print("無效的存款金額。")
# 提款方法
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
print(f"提出 {amount}。新餘額為 {self.__balance}")
else:
print("無效的提款金額或餘額不足。")
# 查詢餘額方法
def get_balance(self):
return self.__balance
# 建立一個銀行帳戶物件
account = BankAccount("1234567890", 1000)
account.deposit(500)
account.withdraw(200)
print(f"當前餘額: {account.get_balance()}")
BankAccount 類別圖
圖表描述 (Alt Text): 此類別圖展示了 BankAccount 類別的結構,包含私有屬性 __account_number 和 __balance,以及公開方法 deposit、withdraw 和 get_balance,體現了物件導向的封裝原則。
@startuml
!theme _none_
skinparam dpi auto
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam minClassWidth 100
skinparam defaultFontSize 14
title BankAccount 類別圖
class BankAccount {
- __account_number: str
- __balance: float
+ __init__(account_number, balance)
+ deposit(amount: float): void
+ withdraw(amount: float): void
+ get_balance(): float
}
@enduml
繼承 (Inheritance) 與多型 (Polymorphism)
繼承允許一個類別(子類別)獲取另一個類別(父類別)的屬性和方法。多型則允許子類別以自己的方式重新實現父類別的方法。
C# 範例:
// 父類別
public class Vehicle {
public int Wheels { get; set; }
public string FuelType { get; set; }
// 可被子類別覆寫的虛擬方法
public virtual void DisplayDetails() {
Console.WriteLine($"輪子數量: {Wheels}, 燃料類型: {FuelType}");
}
}
// 子類別,繼承自 Vehicle
public class Truck : Vehicle {
public int CargoCapacity { get; set; }
// 覆寫父類別的方法,實現多型
public override void DisplayDetails() {
base.DisplayDetails(); // 呼叫父類別的方法
Console.WriteLine($"載貨容量: {CargoCapacity} 噸");
}
}
Vehicle 與 Truck 繼承關係類別圖
圖表描述 (Alt Text): 此類別圖展示了 Truck 類別如何繼承自 Vehicle 父類別,並覆寫了 DisplayDetails 方法,體現了物件導向的繼承與多型特性。
@startuml
!theme _none_
skinparam dpi auto
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam minClassWidth 100
skinparam defaultFontSize 14
title 繼承與多型範例
class Vehicle {
+ Wheels: int
+ FuelType: string
+ virtual DisplayDetails(): void
}
class Truck extends Vehicle {
+ CargoCapacity: int
+ override DisplayDetails(): void
}
@enduml
資料函式庫與資料處理
在軟體開發中,資料是應用程式的生命線。選擇合適的資料函式庫和設計良好的資料架構對於應用程式的效能和可擴充套件性至關重要。
資料函式庫選擇
選擇資料函式庫時,需要考慮資料型別、流量預期、預算以及開發團隊的經驗。以下是一些提示:
- 對於處理大量使用者互動或即時資料的應用程式,考慮使用NoSQL資料函式庫,如MongoDB。
- 對於需要強一致性和複雜事務支援的應用程式,關係型資料函式庫如MySQL或PostgreSQL可能是更好的選擇。
資料庫綱要設計
設計資料庫綱要時,需要定義資料表、欄位以及它們之間的關聯。
SQL 範例:
-- 建立使用者資料表
CREATE TABLE Users (
UserID INT PRIMARY KEY,
Name VARCHAR(255),
Email VARCHAR(255) UNIQUE
);
-- 建立訂單資料表,並設定外鍵關聯
CREATE TABLE Orders (
OrderID INT PRIMARY KEY,
UserID INT,
OrderDate DATE,
FOREIGN KEY (UserID) REFERENCES Users(UserID)
);
使用者與訂單實體關係圖 (ERD)
圖表描述 (Alt Text): 此實體關係圖展示了 Users 和 Orders 兩個實體之間的「一對多」關係,一個使用者可以擁有多筆訂單。
@startuml
!theme _none_
skinparam dpi auto
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam minClassWidth 100
skinparam defaultFontSize 14
title 使用者與訂單 ER 圖
entity Users {
* UserID (PK)
--
Name
Email
}
entity Orders {
* OrderID (PK)
--
UserID (FK)
OrderDate
}
Users ||--o{ Orders : "擁有"
}
@enduml
總之,物件導向程式設計、框架與函式庫的使用,以及資料函式庫的選擇和設計,都是軟體開發中不可或缺的重要環節。掌握這些核心概念和技術,將有助於開發出高效、可維護的軟體應用程式。