pta兩次大作業

NCHU-徐小磊發表於2024-11-19

PTA 兩次大作業總結:詳細分析與實踐經驗

前言

回顧這次的傢俱強電電路模擬程式大作業,它無疑是一次極具挑戰的程式設計與設計經歷。從最初簡單的電路元件模擬,到後期複雜的多裝置連線和精準的控制反饋,這個過程不僅讓我掌握了許多技術技能,還在思維方式、問題解決能力以及系統設計方面得到了顯著提升。

初期階段:從基礎模擬到逐步擴充套件
作業的前期階段主要集中在基本電路元件的模擬。最初,我的任務是將開關、燈具、電動裝置等元素抽象成程式中的類,並讓它們在程式碼中能夠模擬實際電路的執行。雖然這些裝置功能簡單,但如何有效地管理和控制它們的狀態,卻並非易事。尤其是如何模擬開關的不同狀態變化,以及如何精確地控制燈具的亮滅、電動裝置的啟停,都讓我經歷了不小的困難。

隨著作業的進行,我逐步掌握瞭如何透過物件導向的方式,結合繼承、封裝和多型等基本概念,來設計和管理這些裝置。透過將不同裝置抽象成類,並透過方法來控制它們的行為,我得以逐步構建起一個較為簡易的電路模擬程式。這個過程不僅鍛鍊了我的程式設計基礎,還讓我更加熟悉了物件導向程式設計的核心原則。

中期階段:電路連線與動態狀態管理
然而,隨著作業逐漸深入,第二階段的任務變得複雜得多。如何將多個裝置之間的相互連線和狀態同步起來,成為了我面臨的最大挑戰。尤其是在類比電路的並聯與串聯時,如何確保裝置之間的電壓、電流傳遞符合實際物理規律,並能動態更新每個裝置的狀態,成為了難點。此時,我不僅需要考慮每個裝置的獨立行為,還需要確保它們在電路中的互動符合預期。

這時候,命令解析和輸入格式的嚴格要求讓我陷入了困境。每一次裝置的狀態變化,都必須精準反饋給其他連線的裝置,稍有偏差就會導致整個電路的崩潰。我記得當我初次處理這些電路連線時,因為忽略了某些關鍵的邊界條件,導致程式的輸出結果完全不符合預期,甚至連電壓值和電流的計算都會出錯。每當遇到這種情形,我的心情都會變得極為焦慮,甚至有些想放棄,但每一次除錯解決問題後,那種從困境中走出的成就感,又讓我重新燃起了動力。

後期階段:精細的異常處理與模組化設計
進入到第三階段,我的挑戰逐漸集中在如何最佳化程式的異常處理和如何設計更加模組化的結構上。面對越來越複雜的電路連線,我意識到如果將所有的功能集中在一個類中,程式碼不僅容易變得冗長且難以維護,而且除錯時也會變得異常困難。因此,我開始將不同的功能進行拆分,嘗試將裝置的控制、命令的解析以及電路狀態的更新分別封裝在不同的類和方法中。透過這種模組化的設計,我不僅提高了程式碼的可讀性,也極大地提升了程式碼的可擴充套件性。即使在最後的除錯階段,我也能夠輕鬆定位到問題並進行相應的修改和最佳化。
在此過程中,我也深刻理解了異常處理的重要性。面對不合法的輸入、裝置狀態的異常變化,或者電路連線中的突發錯誤,我都需要透過合適的異常捕獲機制來確保系統能夠穩定執行。剛開始時,我覺得加入過多的異常處理會使程式碼顯得冗餘,但隨著除錯過程的深入,我漸漸明白了“防禦性程式設計”對於系統健壯性的重要性。透過對每一個潛在問題的預防,程式變得更加穩健,也讓我在除錯時少了很多焦慮。
程式設計思維的提升:從錯誤到經驗的積累
當然,在整個過程中也充滿了挑戰。特別是在處理電路裝置連線時,我曾一度卡住,無法理解如何同步多個裝置的狀態。最初,我誤解了裝置連線的邏輯,導致電壓、電流的計算頻繁出錯,進而引發了多次除錯失敗。每一次面對困境時,我都感到深深的沮喪,但也正是透過這些錯誤的積累,我才逐漸對電路原理和程式結構有了更加深入的理解。每當除錯成功,程式終於能夠正常執行時,那種從困境中迎來突破的喜悅,常常讓我感到無比滿足。
這些錯誤和困難,雖然讓我在程式設計過程中感受到了挫敗,雖然最終我的pta第二次作業並沒有及格,但它們也推動我不斷深入思考程式設計的本質,促使我不斷精進自己的技術。從最初的“誤打誤撞”,到現在能夠有條理地拆解問題,設計合理的程式架構,整個過程中我逐漸意識到,程式設計不僅僅是寫出能執行的程式碼,更是一個不斷最佳化與改進的過程。

未來展望:不斷提升的程式設計能力
回顧整個作業過程,我深刻體會到,程式設計不僅是技術層面的積累,它更是一種思維方式的錘鍊。這三次作業讓我從最基礎的電路元件模擬,逐步走向多裝置管理、電路狀態控制等更復雜的功能設計。這些技能的掌握,使我不僅能夠獨立完成相對複雜的程式設計任務,還能夠更加自信地面對未來更高難度的專案。

未來,我希望能夠進一步提升自己的程式碼最佳化能力,學習並應用更為高階的設計模式,以便在面對更加複雜的系統時,能夠設計出更高效、易於維護的程式碼。同時,我也希望在測試方法的應用上不斷探索,力求讓每一段程式碼都經得起多場景、多輸入的考驗。

這次的作業讓我經歷了無數次的“痛並快樂著”,但每一次的突破,都讓我更加熱愛程式設計,並讓我相信程式設計是一個可以不斷挑戰自己、提升自己的過程。正如我在這篇總結中所提到的,程式設計不僅是一個技能的學習,它更是一個自我探索與自我提升的旅程,而我,才剛剛開始。

設計與分析

家居強電電路模擬程式-1

任務描述

本專案的任務是模擬家居強電電路,包括對多種家居裝置(如開關、調速器、電燈等)的電壓控制和狀態管理。透過裝置間的連線與控制命令,模擬家居電路的實際工作流程,實現各種裝置的狀態變化並展示給使用者。具體要求包括:

  • 支援多個裝置,能夠根據輸入的電壓進行狀態計算。
  • 支援裝置間的連線(如開關與燈、調速器與風扇等)。
  • 支援控制命令來改變裝置的狀態(如開關開關、調速器增減速度等)。
  • 輸出裝置的當前狀態。

類設計

1. Device

特色程式碼:

abstract class Device {
    protected String id;
    protected double inputVoltage;  // 輸入電壓
    protected double outputVoltage; // 輸出電壓

    public Device(String id) {
        this.id = id;
        this.inputVoltage = 0;
        this.outputVoltage = 0;
    }

    public abstract void process(); // 處理裝置的電壓輸出
    public abstract String getStatus(); // 獲取裝置狀態

    public void setInputVoltage(double input) {
        this.inputVoltage = input;
    }

    public double getOutputVoltage() {
        return outputVoltage;
    }

    public void setOutputVoltage(double output) {
        this.outputVoltage = output;
    }

    public String getId() {
        return id;
    }
}

分析:
Device 類是所有裝置的父類,它定義了裝置的基本屬性(如 idinputVoltageoutputVoltage)以及通用的行為(如設定電壓、獲取電壓和獲取裝置狀態)。透過抽象方法 process()getStatus(),每個裝置都可以根據自身的特性來實現具體的行為。這是一個典型的物件導向設計中的抽象類,它保證了不同裝置類的統一介面,同時又能提供各自的具體實現。

2. Switch

特色程式碼:

class Switch extends Device {
    private boolean isClosed;

    public Switch(String id) {
        super(id);
        this.isClosed = false;
    }

    @Override
    public void process() {
        this.outputVoltage = isClosed ? inputVoltage : 0;
    }

    @Override
    public String getStatus() {
        return isClosed ? "closed" : "turned on";
    }

    public void toggle() {
        isClosed = !isClosed;
    }
}

分析:
Switch 類繼承了 Device 類,模擬了一個簡單的開關裝置。透過 toggle() 方法改變 isClosed 狀態,從而控制電流的通斷。process() 方法根據開關的狀態計算輸出電壓,getStatus() 方法返回開關的當前狀態(“closed” 或 “turned on”)。這種設計體現了開關裝置的典型功能:開關裝置的電壓輸出由其自身的開關狀態決定。

3. StepSpeedController

特色程式碼:

class StepSpeedController extends Device {
    private int level;

    public StepSpeedController(String id) {
        super(id);
        this.level = 0;
    }

    @Override
    public void process() {
        double multiplier = 0;
        switch (level) {
            case 1:
                multiplier = 0.3;
                break;
            case 2:
                multiplier = 0.6;
                break;
            case 3:
                multiplier = 0.9;
                break;
            default:
                multiplier = 0;
                break;
        }
        this.outputVoltage = inputVoltage * multiplier;
    }

    @Override
    public String getStatus() {
        return String.valueOf(level);
    }

    public void increaseLevel() {
        if (level < 3) level++;
    }

    public void decreaseLevel() {
        if (level > 0) level--;
    }
}

分析:
StepSpeedController 類模擬了一個分檔調速器裝置,透過不同的檔位(level)調節電壓輸出。process() 方法根據檔位 level 來設定輸出電壓的倍率,getStatus() 返回當前檔位。透過 increaseLevel()decreaseLevel() 方法調整檔位,這樣就能動態地改變電壓輸出。這種設計適合應用在控制速度、亮度等需要調節的裝置。

4. IncandescentLamp

特色程式碼:

class IncandescentLamp extends Device {
    private static final double MAX_VOLTAGE = 220;
    double value = 0;

    public IncandescentLamp(String id) {
        super(id);
    }

    @Override
    public void process() {
        double voltageDiff = Math.abs(inputVoltage);

        if(inputVoltage <= 9 && inputVoltage >= 0)
            value = 0;
        else if(inputVoltage == 10)
            value = 50;
        else if(inputVoltage == 220)
            value = 200;
        else if(inputVoltage > 10 && inputVoltage < 220) {
            double k = (200.0 - 50.0) / (220.0 - 10.0);
            double b = 50.0 - k * 10.0;
            value = inputVoltage * k + b;
        }
        outputVoltage = 0;
    }

    @Override
    public String getStatus() {
        return String.valueOf((int) value);
    }
}

分析:
IncandescentLamp 類模擬了一個白熾燈裝置。白熾燈的亮度與輸入電壓相關,這裡透過輸入電壓來計算亮度的數值(value)。當電壓在 10V 到 220V 之間時,亮度是一個線性函式,透過求解該函式計算亮度。process() 方法根據輸入電壓計算亮度並更新狀態,getStatus() 返回當前的亮度值。這一設計反映了裝置的物理特性,並且將狀態更新的邏輯封裝在 process() 方法中。

5. CircuitSimulator

特色程式碼:

class CircuitSimulator {
    private Map<String, Device> devices = new HashMap<>();
    private List<String> connections = new ArrayList<>();
    private List<String> commands = new ArrayList<>();

    public void parseConnections() {
        for (String connection : connections) {
            String[] parts = connection.replaceAll("[\\[\\]]", "").split(" ");
            if (parts.length == 2) {
                String dev1Id = parts[0];
                String dev2Id = parts[1];

                if (dev1Id.equals("VCC")) {
                    String dev2BaseId = dev2Id.split("-")[0];
                    Device dev2 = devices.get(dev2BaseId);
                    if (dev2 != null) {
                        dev2.setInputVoltage(220);  // 設定 VCC 電壓為 220V
                        dev2.process();
                    }
                } else if (dev2Id.equals("GND")) {
                    String dev1BaseId = dev1Id.split("-")[0];
                    Device dev1 = devices.get(dev1BaseId);
                    if (dev1 != null) {
                        dev1.setOutputVoltage(0);  // 設定 GND 電壓為 0V
                        dev1.process();
                    }
                } else {
                    String dev1BaseId = dev1Id.split("-")[0];
                    String dev2BaseId = dev2Id.split("-")[0];

                    Device dev1 = devices.get(dev1BaseId);
                    Device dev2 = devices.get(dev2BaseId);

                    if (dev1 != null && dev2 != null) {
                        dev1.process();
                        dev2.setInputVoltage(dev1.getOutputVoltage());
                        dev2.process();
                    }
                }
            }
        }
    }
}

分析:
CircuitSimulator 類是整個模擬器的核心,負責裝置之間的連線、命令的執行以及裝置狀態的更新。它透過 connections 列表管理裝置之間的連線,透過 commands 列表執行使用者輸入的控制命令。在解析連線時,判斷裝置是否連線到電源(VCC)或地(GND),並根據連線的情況調整裝置的輸入和輸出電壓。parseConnections() 方法根據裝置的連線關係動態地調整裝置之間的電壓流動,確保模擬的電路是符合實際的。

6. Main

特色程式碼:

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        CircuitSimulator simulator = new CircuitSimulator();

        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            if (line.equals("end")) break;

            if (line.startsWith("#")) {
                simulator.addCommand(line);
            } else if (line.startsWith("[") && line.endsWith("]")) {
                simulator.addConnection(line);
            }
        }

        simulator.parseCommands();
        simulator.parseConnections();
        simulator.showAllDevicesStatus();
    }
}

分析:
Main 類中,使用 Scanner 讀取使用者輸入,

直到使用者輸入 end。輸入的每一行都可能是一個命令或者連線。連線用 [] 括起來,命令以 # 開頭。透過這些輸入,模擬器動態地解析連線和命令,最終根據裝置之間的連線關係和命令來更新每個裝置的狀態。

程式碼複雜度與質量分析(基於 SourceMonitor)

在第一次大作業中,我使用了 SourceMonitor 對程式碼進行了詳細的複雜度分析,這裡給出了專案的整體質量指標:

分析圖表:

從 SourceMonitor 提供的報告和兩個圖表中,我們可以分析程式碼的複雜度和結構特性,並進一步思考如何最佳化程式碼質量。

1. 複雜度雷達圖

複雜度雷達圖展示了程式碼的核心統計資料:

  • 平均複雜度(Avg Complexity):程式碼的平均複雜度為 2.73,屬於較低水平。說明整體程式碼的邏輯不復雜,沒有深層次的巢狀邏輯,符合簡潔性的要求。
  • 最大複雜度(Max Complexity):最高複雜度為 10,出現在 Main.main() 方法中。這表明 main() 方法的邏輯較為集中,可能需要進一步拆分和最佳化。
  • 每類方法數(Methods/Class):每個類平均有 8.4 個方法,說明程式碼功能較為細化,但仍存在進一步解耦的最佳化空間。
  • 每個方法平均語句數(Avg Stmts/Method):每個方法平均有 8.36 條語句,表明方法邏輯適中,較為簡潔,但某些方法可能需要進一步簡化。
  • 註釋覆蓋率(% Comments):註釋覆蓋率僅為 13.4%,這一指標偏低,表明程式碼的可讀性和文件化不足,可能對團隊協作和長期維護造成不利影響。
  • 巢狀深度(Avg Depth & Max Depth):程式碼的平均塊深度為 1.5,最大深度為 6。平均深度較淺,表明程式碼邏輯結構較為扁平,最大深度集中在複雜方法中,需要特別關注。

總體來看,程式碼在複雜度控制方面表現良好,但存在註釋覆蓋率不足和部分複雜方法需要拆分的改進點。


2. 塊深度直方圖

塊深度直方圖展示了程式碼塊的巢狀深度分佈:

  • 塊深度為 0 到 2:絕大部分程式碼塊(284 個語句)的巢狀深度集中在 0 至 2。這表明程式碼的邏輯較為簡單直觀,避免了過多的層級巢狀,從而提高了程式碼的可讀性和維護性。
  • 塊深度為 3 到 5:少部分程式碼塊(72 個語句)的深度為 3 到 5,這些地方可能是控制邏輯集中或複雜的地方。
  • 塊深度為 6 及以上:只有極少數程式碼塊達到深度 6。這些深度較大的程式碼塊可能引入了複雜的邏輯巢狀,需要重點檢查是否存在進一步最佳化的可能性。

這表明程式碼的邏輯設計偏向扁平化,整體結構簡潔。但需要注意深度為 3 及以上的程式碼塊是否引入了難以維護的複雜性,尤其是出現在關鍵方法中的複雜塊。


3. 複雜度較高的方法分析

從 SourceMonitor 報告中提取的複雜方法資料:

方法名稱 複雜度 語句數 最大深度 方法呼叫數
FloorFan.updateState() 6 11 3 0
FluorescentLamp.updateState() 3 5 3 0
IncandescentLamp.updateState() 4 8 3 0
Main.main() 10 36 6 28
  • Main.main() 方法

    • 複雜度為 10,是程式碼中複雜度最高的方法。
    • 語句數達到 36,且最大深度為 6,有 28 次方法呼叫。這說明 main() 方法中包含了大量的邏輯操作和裝置初始化、命令解析等任務。
    • 日後的改進建議Main.main() 方法職責過重,可以考慮將初始化和命令解析邏輯拆分成獨立的方法或類。例如:
      1. 提取裝置初始化到專門的 DeviceInitializer 類。
      2. 將命令解析和執行的邏輯封裝到一個 CommandHandler 類中。
      3. 保持 main() 方法僅負責高層的流程控制。
  • FloorFan.updateState() 方法

    • 複雜度為 6,語句數為 11,巢狀深度為 3。這可能是因為該方法對輸入電壓進行多條件判斷並更新狀態。
    • 改進建議:可以嘗試將複雜的邏輯分解為多個輔助方法,例如單獨處理電壓條件的判斷邏輯。

4. 程式碼複雜度總結

優點

  1. 整體複雜度較低:平均複雜度 2.73,最大複雜度 10,說明程式碼邏輯簡單,避免了深度巢狀和複雜操作。
  2. 模組化設計較好:每個類封裝了裝置的獨立邏輯,類與類之間透過輸入電壓和連線關係互動,體現了良好的物件導向設計。
  3. 塊深度控制良好:絕大多數程式碼塊的深度集中在 0 到 2,表明程式碼結構清晰,易於閱讀和維護。

改進方向

  1. 註釋覆蓋率偏低:註釋覆蓋率僅為 13.4%,需要增加對方法、類和複雜邏輯的註釋,幫助開發者更好地理解程式碼。
  2. 複雜方法的職責分離Main.main() 方法複雜度高,邏輯集中,建議將其拆分成更小、更獨立的模組。
  3. 提高程式碼複用性:對於裝置之間類似的邏輯(如狀態更新和輸入電壓處理),可以考慮提取公共父類或工具方法,減少程式碼重複。

說實話,第一次作業看上去很簡單,但涉及到準確解析輸入的時候,我還是遇到了一些麻煩。特別是解析輸入,原來的邏輯常常崩潰,除錯的過程比我預想的要痛苦得多。

類圖分析(基於 PowerDesigner)


類及其結構

1. Device

屬性

  • protected String id:裝置的唯一識別符號。
  • protected double inputVoltage:裝置的輸入電壓。
  • protected double outputVoltage:裝置的輸出電壓。

構造方法

  • public Device(String id):初始化裝置,設定 idinputVoltageoutputVoltage 預設為 0。

方法

  • public abstract void process():處理電壓輸出的抽象方法。
  • public abstract String getStatus():返回裝置的狀態資訊的抽象方法。
  • public void setInputVoltage(double input):設定裝置的輸入電壓。
  • public double getOutputVoltage():獲取裝置的輸出電壓。
  • public void setOutputVoltage(double output):設定裝置的輸出電壓。
  • public String getId():獲取裝置的 ID。

2. Switch

繼承自 Device

屬性

  • private boolean isClosed:開關是否閉合。

構造方法

  • public Switch(String id):初始化開關裝置,設定 isClosedfalse

方法

  • public void process():處理電壓輸出,若開關閉合,輸出電壓與輸入電壓相同,否則輸出 0。
  • public String getStatus():返回開關的狀態(closedturned on)。
  • public void toggle():切換開關的狀態。

3. StepSpeedController

繼承自 Device

屬性

  • private int level:調速器的檔位(1-3)。

構造方法

  • public StepSpeedController(String id):初始化調速器,設定檔位為 0。

方法

  • public void process():根據檔位調整電壓輸出。
  • public String getStatus():返回當前檔位的狀態。
  • public void increaseLevel():增加檔位。
  • public void decreaseLevel():減少檔位。

4. ContinuousSpeedController

繼承自 Device

屬性

  • private double position:調速器的位置(0.0 到 1.0)。

構造方法

  • public ContinuousSpeedController(String id):初始化調速器,設定位置為 0.0。

方法

  • public void process():根據位置調整電壓輸出。
  • public String getStatus():返回當前的位置(0.00 到 1.00)。
  • public void setPosition(double position):設定調速器的位置。

5. IncandescentLamp

繼承自 Device

屬性

  • private static final double MAX_VOLTAGE = 220:最大電壓。
  • private double value:光強度。

構造方法

  • public IncandescentLamp(String id):初始化白熾燈。

方法

  • public void process():根據輸入電壓計算光強度。
  • public String getStatus():返回當前的光強度。

6. FluorescentLamp

繼承自 Device

屬性

  • private double value:光強度。

構造方法

  • public FluorescentLamp(String id):初始化日光燈。

方法

  • public void process():根據輸入電壓是否大於 0 來決定輸出的光強度。
  • public String getStatus():返回當前的光強度。

7. Fan

繼承自 Device

屬性

  • private static final double MIN_VOLTAGE = 80:最小電壓。
  • private static final double MAX_VOLTAGE = 150:最大電壓。
  • private double value:風扇的輸出電壓。

構造方法

  • public Fan(String id):初始化風扇裝置。

方法

  • public void process():根據輸入電壓計算風扇輸出電壓。
  • public String getStatus():返回當前的風扇輸出電壓。

8. CircuitSimulator

屬性

  • private Map<String, Device> devices:儲存所有裝置的對映。
  • private List<String> connections:儲存裝置間連線的列表。
  • private List<String> commands:儲存執行的命令列表。

構造方法

  • public CircuitSimulator():初始化裝置集合、連線資訊和命令列表。

方法

  • public void addDevice(String deviceId):根據裝置 ID 動態新增裝置。
  • public void addConnection(String connection):新增裝置之間的連線。
  • public void addCommand(String command):新增裝置命令。
  • public void parseConnections():解析裝置連線並設定電壓。
  • public void parseCommands():執行裝置命令。
  • public void showAllDevicesStatus():顯示所有裝置的狀態。

關係示意

繼承關係

  • SwitchStepSpeedControllerContinuousSpeedControllerIncandescentLampFluorescentLampFan 都繼承自 Device

聚合關係

  • CircuitSimulator 聚合 Device,管理多個裝置物件。

依賴關係

  • CircuitSimulator 依賴 Device 類,使用它的 process()getStatus() 方法來類比電路。

設計心得

在開發 家居強電電路模擬程式 的過程中,我感受到了一次完整的設計、實現和最佳化過程所帶來的啟發與成長。這不僅僅是對程式碼邏輯的深入探索,更是對設計原則與編碼習慣的實踐和反思。以下是我的一些心得體會:


1. 模組化設計的重要性

在程式中,不同型別的裝置(如開關、調速器、燈具、風扇等)透過繼承一個共同的抽象類 Device 實現了功能的模組化。這種設計讓我深刻認識到:

  • 抽象是複雜系統的簡化器:透過將裝置的通用功能抽象為父類,具體的實現邏輯被分散到各個子類中,不僅增強了程式碼的可讀性,也降低了系統的耦合度。
  • 職責單一原則(SRP):每個類只處理自己的職責,比如 Switch 負責控制通斷,StepSpeedController 負責調速,這種明確的分工使得程式碼在邏輯上更容易維護和擴充套件。

啟發:模組化設計幫助我更好地組織程式碼,尤其是當專案規模擴大時,清晰的類劃分和職責分離將顯得尤為重要。這種思路讓我對物件導向設計原則有了更直觀的理解。


2. 平衡靈活性與複雜性

在實現 CircuitSimulator 時,我一開始嘗試將所有裝置連線的邏輯寫死,但發現這樣會導致擴充套件性極差。例如,當需要支援新裝置型別時,我不得不修改核心程式碼。後來透過動態地將裝置型別與具體類關聯,我實現了更靈活的設計:

char type = baseId.charAt(0);
switch (type) {
    case 'K': device = new Switch(baseId); break;
    case 'F': device = new StepSpeedController(baseId); break;
    ...
}

透過這種設計,當需要引入新裝置型別時,只需新增一個裝置類並修改少量程式碼即可。

啟發:靈活性通常需要付出複雜性的代價,但在系統架構中,適度的靈活性是必要的。透過抽象工廠模式或配置檔案初始化等方式,可以進一步降低硬編碼的複雜性,提升程式碼的適應性。


3. 複雜性管理

在程式中,Main.main() 方法一度成為複雜性聚集點。由於它負責裝置初始化、連線解析、命令執行等多項任務,導致方法長度和複雜度急劇增加。這讓我認識到:

  • 程式碼複雜性不應集中於單一方法或類:當一個方法負責過多的職責時,不僅難以閱讀和維護,還會使程式碼更容易出錯。
  • 分解複雜性,擁抱分層架構:透過將複雜的邏輯拆分到獨立的類中(如 CommandHandlerConnectionParser),主流程邏輯得以保持清晰。雖然增加了類的數量,但程式碼的結構性得到了顯著改善。

啟發:複雜性是程式設計的天敵,而解耦是對抗複雜性的重要武器。在未來的設計中,我將更加註重程式碼的分層與職責分離,避免複雜邏輯的集中。


4. 註釋與文件的價值

在實現過程中,我注意到註釋覆蓋率僅為 13.4%,這一指標直接影響了程式碼的可讀性。尤其是當程式碼中包含複雜的邏輯或數學計算(如白熾燈亮度的線性插值計算)時,缺少註釋會讓後來者難以快速理解其含義。

透過後期的補充註釋,我發現:

  • 註釋不僅是對他人的幫助,更是對自己的提醒:註釋可以記錄設計思路和關鍵邏輯,從而幫助未來的維護者理解程式碼的設計意圖。
  • 好的註釋是解釋“為什麼”,而不是“是什麼”:註釋不應僅描述程式碼表面的含義,而是需要解釋背後的設計思路和決策依據。

啟發:良好的註釋是高質量程式碼的重要組成部分。未來在編碼時,我將養成及時新增註釋的習慣,特別是在處理複雜邏輯時,確保每一段程式碼都有清晰的解釋。


家居強電電路模擬程式-2

任務描述

家居強電電路模擬程式的第二版,總算是讓我體驗了一把“上天入地”的感覺。比起第一版,這次的要求明顯更復雜,啥串聯電路、並聯電路,裝置之間還開始搞電阻、搞電壓差,甚至還要計算燈的亮度和風扇的轉速。別的不說,光是看完題目,我就差點覺得“要瘋了”。

不過仔細想想,也挺帶勁。這次設計不僅要搞定複雜的電路拓撲,還得保證每個裝置都按它的“個性”來工作。我一邊設計類結構,一邊腦補裝置的實際執行邏輯,感覺就像是在給一套智慧家居系統寫作業系統似的,滿腦子都是電路和程式碼。


1. ElectricDevice 類:裝置的靈魂基石

所有裝置的共同點都藏在這個基類裡。它像是一個裝置的模板,把輸入、輸出、電阻等共性抽象出來。這個基類的存在,可以讓我優雅地對各種裝置“一視同仁”。

特色程式碼
public abstract class ElectricDevice {
    protected String id;
    protected double inputVoltage;
    protected double outputVoltage;
    protected double resistance;

    public ElectricDevice(String id) {
        this.id = id;
        this.inputVoltage = 0.0;
        this.outputVoltage = 0.0;
        this.resistance = 0.0;
    }

    public abstract void updateState();
    public abstract String getStatus();

    public void setInputVoltage(double voltage) {
        this.inputVoltage = voltage;
        updateState();
    }

    public double getOutputVoltage() {
        return outputVoltage;
    }
}
設計思路
  1. 統一輸入與輸出:透過 setInputVoltage() 傳遞電壓,輸出透過 getOutputVoltage() 獲取,邏輯清晰。
  2. 核心邏輯抽象updateState() 是裝置的靈魂方法,裝置的所有行為都透過它實現。
  3. 擴充套件能力:這個基類讓我只需要關心“裝置要做什麼”,而不是“裝置怎麼連線”。每新增一種裝置,只要繼承 ElectricDevice 並實現抽象方法即可。

2. 控制裝置:電路的指揮家

控制裝置的任務很明確——它們用來調節電路的行為,包括開關、分檔調速器和連續調速器。這類裝置的重點是“靈活性”:調節幅度、控制邏輯都要夠直觀。

2.1 開關裝置 SwitchDevice

開關的邏輯就像它的名字:開或關。雖然它是最簡單的裝置,但在整個系統中卻是最關鍵的“守門員”。

特色程式碼
@Override
public void updateState() {
    this.outputVoltage = isClosed ? this.inputVoltage : 0.0;
}

@Override
public String getStatus() {
    return "@" + id + ":" + (isClosed ? "closed" : "turned on");
}
設計亮點
  • 開關的邏輯就是“二元判斷”,沒有花哨的邏輯。
  • 狀態輸出直觀明瞭,便於除錯和追蹤。
  • 作為所有裝置的“開關門員”,它在實現簡潔性的同時,也讓電路行為變得可控。

2.2 分檔調速器 StepDimmer

分檔調速器有 4 個檔位,分別對應 0%、30%、60%、90% 的輸出電壓。它像是電路中的“齒輪箱”,讓我可以手動調整輸出。

特色程式碼
@Override
public void updateState() {
    double[] multipliers = {0.0, 0.3, 0.6, 0.9};
    this.outputVoltage = this.inputVoltage * multipliers[level];
}

@Override
public String getStatus() {
    return "@" + id + ":" + level;
}
設計亮點
  • 透過一個簡單的 multipliers 陣列實現檔位倍率對映,不需要寫一堆 if-else
  • 每次切換檔位後自動更新輸出狀態,邏輯流暢,使用方便。
  • 輸出狀態以檔位為核心,讓狀態變得清晰可見。

2.3 連續調速器 ContinuousDimmer

連續調速器比分檔調速器“高大上”了不少,它可以以任意比例輸出電壓。為了保證輸出精確到小數點兩位,我在程式碼裡稍微用了一點小技巧。

特色程式碼
public void setLevel(double level) {
    this.level = Math.floor(level * 100) / 100.0; // 保留兩位小數
    updateState();
}

@Override
public void updateState() {
    this.outputVoltage = this.inputVoltage * this.level;
}
設計亮點
  • 透過 Math.floor() 精確控制檔位比例,輸出結果優雅且規整。
  • 支援任意比例的調節,非常靈活,幾乎可以滿足任何電路需求。
  • 更新邏輯簡單,呼叫 setLevel() 後一切自動完成。

3. 受控裝置:電路的被動成員

受控裝置是電路的“執行者”,它們根據輸入電壓做出反應,比如燈光亮度、風扇轉速。這些裝置的邏輯稍微複雜一些,因為它們需要考慮實際的物理規律。

3.1 白熾燈 IncandescentLamp

白熾燈的亮度是線性變化的,但亮度範圍被限定在 50lux 到 200lux 之間。這段程式碼寫得有點像高中物理題,但最終結果讓我非常滿意。

特色程式碼
@Override
public void updateState() {
    if (inputVoltage <= 9.0) {
        this.outputVoltage = 0.0;
    } else {
        double lux = 50.0 + (inputVoltage - 10.0) * 150.0 / 210.0;
        this.outputVoltage = Math.min(lux, 200.0);
    }
}
設計亮點
  • 利用線性公式精確計算亮度,符合真實物理規律。
  • 透過 Math.min() 限制亮度最大值,防止燈“爆表”。
  • 輸出結果穩定可靠,尤其在電壓邊界值時,表現尤為出色。

3.2 吊扇 CeilingFan

吊扇的轉速與電壓差線性相關,但工作範圍是 80V 到 150V。超過範圍時,轉速保持固定值。

特色程式碼
@Override
public void updateState() {
    if (inputVoltage < 80.0) {
        this.outputVoltage = 0.0;
    } else if (inputVoltage >= 150.0) {
        this.outputVoltage = 360.0;
    } else {
        this.outputVoltage = 80.0 + (inputVoltage - 80.0) * 280.0 / 70.0;
    }
}
設計亮點
  • 電壓與轉速的線性關係體現了吊扇的物理特性,邏輯非常直觀。
  • 透過分段邏輯處理電壓範圍外的情況,確保輸出轉速符合預期。

4. 串聯與並聯電路:連線的藝術

4.1 串聯電路

串聯電路將裝置按順序連線,電壓逐一傳遞。實現時,我直接用了一個裝置順序列表,讓電壓從頭傳到尾,邏輯非常直觀。

特色程式碼
for (String conn : sc.getConnections()) {
    String devId = conn.split("-")[0];
    ElectricDevice dev = devices.get(devId);
    dev.setInputVoltage(currentVoltage);
    currentVoltage = dev.getOutputVoltage();
}
設計亮點
  • 裝置按順序傳遞電壓,模擬了真實的串聯電路。
  • 程式碼結構簡單,擴充套件性強,能夠輕鬆適配更多裝置。

4.2 並聯電路

並聯電路透過等效電阻公式計算總電阻,並按比例分配電壓。這段邏輯的實現讓我回憶起高中物理課上的電路計算公式。

特色程式碼
double reciprocal = 0.0;
for (double r : branchResistances) {
    if (r != 0.0) reciprocal += 1.0 / r;
}
double parallelResistance = reciprocal != 0.0 ? 1.0 / reciprocal : 0.0;
設計亮點
  • 等效電阻公式完美體現了並聯電路的物理規律。
  • 為每個並聯分支動態分配電壓,模擬效果逼真。

5. 輸出設計:程式的最後一公里

我按裝置型別和編號排序輸出狀態,既要美觀也要實用。最終輸出的格式讓我覺得清晰又規範,除錯起來格外舒心。

特色程式碼
String[] order = {"K", "F

", "L", "B", "R", "D", "A"};
for (String type : order) {
    for (ElectricDevice device : categorizedDevices.get(type)) {
        System.out.println(device.getStatus());
    }
}
設計亮點
  • 按裝置型別和編號排序,讓輸出結果邏輯性更強。
  • 每行狀態資訊都清晰直觀,方便排查問題。

總結

這次的設計讓我深刻感受到複雜系統的魅力。每個類都有自己的職責,裝置邏輯與電路關係相輔相成。雖然中途也有迷茫,但每次看著程式碼變得更加簡潔、更加穩定時,我都忍不住感嘆:程式設計還真是一件既燒腦又讓人上癮的事!

程式碼複雜度與質量分析(基於 SourceMonitor)


程式碼複雜度與質量分析(基於 SourceMonitor)

在第二次大作業中,我使用了 SourceMonitor 對程式碼進行了詳細的複雜度分析,這裡給出了專案的整體質量指標:

分析圖表:

從 SourceMonitor 提供的報告和兩個圖表中,我們可以分析程式碼的複雜度和結構特性,並進一步思考如何最佳化程式碼質量。

主要指標與結果

透過 SourceMonitor 工具分析後,我得到了以下一些資料:

  • 程式碼行數:597 行
  • 語句數:303 條
  • 分支語句佔比:23.8%
  • 方法呼叫語句:110 條
  • 註釋行百分比:18.6%
  • 類和介面數量:6 個
  • 每個類的方法數量:平均 7.17
  • 每個方法的平均語句數:6.98 條
  • 最複雜的方法Main.main(),複雜度為 6
  • 最大程式碼塊深度:5
  • 平均程式碼塊深度:2.11
  • 平均複雜度:1.86

看到這些指標後,我意識到程式碼的整體複雜度雖然沒有非常高,但有幾個關鍵方法的複雜性值得關注,尤其是 Main.main() 方法,它的複雜度和塊深度在程式碼中屬於最高的部分。


複雜度雷達圖與塊深度直方圖

複雜度雷達圖(Kiviat 圖):
從複雜度雷達圖中,我可以清楚地看到程式碼在“最大複雜度”和“最大塊深度”上的表現尤為突出。這表明某些方法內部邏輯較為複雜,特別是像 Main.main() 這樣的方法,承擔了較多的功能職責。

塊深度直方圖:
塊深度分佈顯示,大多數程式碼的巢狀深度在 2 到 4 之間,這還算在可接受範圍內。但最大塊深度為 5,這部分程式碼主要集中在一些邏輯較為複雜的方法中,比如 Fan1.process()IncandescentLamp.process()。巢狀深度過大讓我意識到,需要對這些部分進行最佳化。


複雜度較高的方法

1. Main.main() 方法

  • 複雜度:6
  • 語句數:11
  • 最大塊深度:4
  • 方法呼叫次數:10

Main.main() 方法作為主入口,它包含了輸入解析、裝置初始化和主邏輯控制等功能。雖然它的複雜度不是特別高,但多層巢狀和多分支處理讓程式碼看起來有些臃腫。我認為,將這一方法的邏輯拆分成多個輔助方法會更加清晰。

2. IncandescentLamp.process() 方法

  • 複雜度:6
  • 語句數:12
  • 最大塊深度:3
  • 方法呼叫次數:1

這個方法負責模擬白熾燈的狀態更新。由於邏輯中涉及亮度計算和多層條件判斷,複雜度相對較高。我認為可以透過最佳化分支結構,進一步降低它的複雜性。

3. Fan1.process() 方法

  • 複雜度:6
  • 語句數:11
  • 最大塊深度:3
  • 方法呼叫次數:0

這個方法的邏輯主要集中在風扇的速度模擬和狀態更新上。雖然塊深度沒有超過 4,但語句密集且涉及多個邏輯條件。我覺得這裡可以提取公共邏輯,進一步最佳化程式碼的結構。


改進計劃

1. 提高註釋覆蓋率
分析結果顯示,我的註釋覆蓋率僅為 18.6%。這讓我意識到,儘管我在編寫程式碼時可能加了一些註釋,但還是有許多關鍵部分的邏輯沒有被清晰標註。特別是 Main.main() 這樣的重要方法,詳細的註釋可以幫助後續閱讀程式碼的同學或自己更快地理解其中的邏輯。

2. 最佳化複雜方法
針對 Main.main() 和其他複雜度較高的方法,我打算透過以下方式最佳化:

  • 將方法內部的分支邏輯拆分為多個輔助方法。例如,可以把“初始化裝置”和“解析輸入”的功能分開處理。
  • 簡化多層巢狀,避免塊深度過大。

3. 減少巢狀深度
巢狀深度大的程式碼塊非常影響閱讀和維護。我準備透過以下措施改進:

  • 提取重複邏輯,將其封裝為獨立的方法。
  • 使用狀態模式來處理條件邏輯,減少巢狀。

4. 改進模組化設計
目前,有些類的職責較多,比如 Main 類和裝置相關的類。我計劃透過模組化設計,把不同裝置的邏輯拆分到更小的類中,這樣每個模組都可以專注於完成單一功能。


程式碼評價總結

透過這次複雜度分析,我對自己的程式碼有了更深入的瞭解。雖然整體程式碼的複雜度尚可,但某些關鍵方法如 Main.main() 的設計需要進一步最佳化。我意識到註釋的重要性,同時也認識到程式碼結構的模組化和清晰性對後續維護的價值。

接下來,我會根據 SourceMonitor 的分析結果,逐步提高程式碼的註釋覆蓋率,最佳化複雜方法,並減少程式碼塊的巢狀深度。這不僅能讓程式碼更加易讀和易維護,也能讓我在程式設計能力上更進一步。

類圖分析(基於 PowerDesigner)

類圖詳細構成

1. ElectricDevice

屬性
protected String id:裝置ID。
protected double inputVoltage:輸入電壓。
protected double outputVoltage:輸出電壓。
protected double resistance:電阻。

構造方法
public ElectricDevice(String id):初始化裝置ID,輸入電壓、輸出電壓、和電阻的預設值為 0.

方法
public abstract void updateState():抽象方法,用於更新裝置狀態。
public void setInputVoltage(double voltage):設定輸入電壓並呼叫 updateState() 更新裝置狀態。
public double getOutputVoltage():返回輸出電壓。
public String getId():返回裝置ID。
public abstract String getStatus():抽象方法,用於獲取裝置的狀態字串。


2. ControlDevice(繼承 ElectricDevice

構造方法
public ControlDevice(String id):繼承自 ElectricDevice,初始化裝置ID。

方法
繼承自 ElectricDevice 的所有方法。


3. SwitchDevice(繼承 ControlDevice

屬性
private boolean isClosed:裝置開關狀態(關閉/開啟)。

構造方法
public SwitchDevice(String id):初始化開關狀態為 "turned on"(即 false)。

方法
public void toggle():切換開關狀態(開啟或關閉)。
public void updateState():根據開關狀態更新輸出電壓(開啟時輸出電壓等於輸入電壓,關閉時輸出電壓為0)。
public String getStatus():返回開關的狀態字串。


4. StepDimmer(繼承 ControlDevice

屬性
private int level:調光器的檔位(0-3)。

構造方法
public StepDimmer(String id):初始化檔位為 0。

方法
public void increaseLevel():增加檔位。
public void decreaseLevel():減少檔位。
public void updateState():根據檔位更新輸出電壓(不同檔位有不同的電壓倍數)。
public String getStatus():返回撥光器的狀態字串(顯示檔位)。


5. ContinuousDimmer(繼承 ControlDevice

屬性
private double level:調光器的檔位(0.00-1.00)。

構造方法
public ContinuousDimmer(String id):初始化檔位為 0.0。

方法
public void setLevel(double level):設定檔位,範圍為 0.00 到 1.00。
public void updateState():根據檔位更新輸出電壓。
public String getStatus():返回撥光器的狀態字串(顯示檔位)。


6. ControlledDevice(繼承 ElectricDevice

構造方法
public ControlledDevice(String id):初始化裝置ID。

方法
繼承自 ElectricDevice 的所有方法。


7. IncandescentLamp(繼承 ControlledDevice

構造方法
public IncandescentLamp(String id):初始化電阻為 10.0。

方法
public void updateState():根據輸入電壓計算輸出電壓。電壓差小於 9V 時,輸出 0V。
public String getStatus():返回燈的狀態(電壓值)。


8. FluorescentLamp(繼承 ControlledDevice

構造方法
public FluorescentLamp(String id):初始化電阻為 5.0。

方法
public void updateState():根據輸入電壓計算輸出電壓(0V 或 180V)。
public String getStatus():返回燈的狀態(電壓值)。


9. CeilingFan(繼承 ControlledDevice

構造方法
public CeilingFan(String id):初始化電阻為 20.0。

方法
public void updateState():根據輸入電壓計算輸出電壓(電壓在 80V 到 360V 之間)。
public String getStatus():返回風扇的狀態(電壓值)。


10. FloorFan(繼承 ControlledDevice

構造方法
public FloorFan(String id):初始化電阻為 20.0。

方法
public void updateState():根據輸入電壓計算輸出電壓(從 80V 到 360V 之間的多個檔位)。
public String getStatus():返回風扇的狀態(電壓值)。


11. SerialCircuit

屬性
private String id:串聯電路ID。
private List<String> connections:裝置連線列表。

構造方法
public SerialCircuit(String id):初始化電路ID和連線列表。

方法
public void addConnection(String connection):新增連線。
public String getId():返回電路ID。
public List<String> getConnections():返回連線列表。


12. ParallelCircuit

屬性
private String id:並聯電路ID。
private List<String> serialCircuitIds:串聯電路ID列表。

構造方法
public ParallelCircuit(String id):初始化電路ID和串聯電路ID列表。

方法
public void addSerialCircuit(String serialId):新增串聯電路。
public String getId():返回並聯電路ID。
public List<String> getSerialCircuitIds():返回串聯電路ID列表。


關係示意

  • 繼承關係

    • ControlDeviceControlledDevice 都繼承自 ElectricDevice,形成 繼承關係
    • SwitchDevice, StepDimmer, ContinuousDimmer, IncandescentLamp, FluorescentLamp, CeilingFan, FloorFan 都繼承自 ControlDeviceControlledDevice,形成 繼承關係
  • 聚合關係

    • SerialCircuitParallelCircuit 各自包含多個裝置ID,形成 聚合關係SerialCircuitParallelCircuitElectricDevice)。


設計心得

  1. 物件導向與繼承的應用
    本次設計採用了物件導向的思想,主要透過繼承和多型實現裝置型別的擴充套件與狀態的管理。透過抽象類 ElectricDevice 和具體子類(如 SwitchDeviceFanDevice 等),實現了不同裝置的統一管理與個性化行為。繼承使得程式碼結構更加清晰,同時避免了重複程式碼,方便後續擴充套件和維護。

  2. 簡化模擬,專注核心功能
    電氣裝置的模擬遵循了簡化原則,沒有考慮過於複雜的電氣原理(如具體的電壓、電流計算),主要模擬裝置的開關、狀態更新和電路連線等功能。這既保證了模擬的簡潔性,也讓系統易於理解和操作。雖然如此,設計時仍儘量保留了現實電路中串聯與並聯的基本概念。

  3. 命令設計與狀態管理
    裝置的控制採用簡短的命令(如 #K#F),這簡化了使用者輸入的複雜度,使得操作更為直觀。狀態更新功能透過多型機制讓不同型別的裝置能夠自行處理狀態變化,避免了冗餘的程式碼邏輯。

  4. 擴充套件性與可維護性
    設計時考慮到未來的擴充套件需求,特別是在裝置型別和電路模型上的擴充套件。透過合理的類結構和介面定義,新裝置可以輕鬆地加入到系統中,且不會影響現有功能。設計時儘量保持了低耦合,高內聚的原則,確保系統具有較好的可維護性。

  5. 潛在的改進方向
    雖然目前的設計已經能滿足基本需求,但在錯誤處理和使用者輸入驗證方面還有待完善。比如,在命令輸入錯誤時,系統應提供友好的錯誤提示或容錯機制。此外,如果未來需要更加精確的電氣模擬(如功率計算、電流分配等),則需要進一步加強底層計算模組的設計。


PTA 家居強電電路模擬程式系列作業總結


踩坑心得

1. 正規表示式解析問題

第一次作業中,輸入格式相對簡單,但由於格式中包含特殊字元(如 []、空格),我初次使用的正規表示式對其處理不夠靈活,導致解析失敗,程式直接崩潰。

  • 問題原因:未正確轉義特殊字元,或未考慮某些輸入的邊界情況。
  • 解決方法:最佳化正規表示式規則,將複雜格式逐步拆解,同時加入異常捕獲機制。例如在解析連線資訊時,先用正規表示式提取 [] 中的內容,再逐一解析每個部分,降低了正規表示式的複雜性。

2. 混合輸入順序管理

第二次作業中需要同時解析串聯電路、並聯電路和裝置資訊。這些資料型別順序混雜,不同型別的輸入相互依賴,而一開始的解析邏輯試圖在一次遍歷中完成所有解析,結果由於輸入順序問題導致部分資料解析失敗。

  • 問題原因:未考慮輸入的依賴關係,例如並聯電路需要串聯電路的完整資訊,裝置連線需要電路拓撲完成後才能解析。
  • 解決方法:採用多輪解析方案:
    1. 第一輪解析裝置資訊和簡單的串聯電路;
    2. 第二輪解析並聯電路資訊,逐條構建子電路;
    3. 最後一輪將所有電路拼接成完整的主電路。
      透過多輪解析,我將依賴關係按順序拆解,保證了每一部分的資料在下一輪解析前完整無誤。

3. 混合電路的電壓計算

第二次作業中最大難點在於電路計算,尤其是串聯與並聯電路的混合情況。並聯電路中需要計算等效電阻,再結合串聯關係分配電壓。最初程式碼直接按裝置遍歷電路,忽略了電路分支的存在,導致結果不正確。

  • 問題原因:沒有正確處理並聯電路的等效電阻,導致電壓分配錯誤。
  • 解決方法
    1. 並聯電阻計算:對並聯電路的所有分支計算等效電阻:
    2. 電壓分配:並聯分支電壓相等,電流按分支電阻反比分配。
    3. 串聯關係:按照電流恆定的原則,從電源逐步傳播電壓,並更新裝置狀態。
      透過這種分段計算的方式,最終實現了混合電路的正確電壓分配。

4. 輸入解析與裝置連線

輸入解析中包含裝置的動態新增和連線關係的解析。例如 [K1-1 K2-2] 表示將裝置 K1 的 1 引腳與裝置 K2 的 2 引腳相連。一開始的實現中,解析直接將連線關係新增到裝置,而沒有檢查連線合法性,導致部分裝置重複連線或漏連線。

  • 問題原因:未對裝置連線的合法性和順序進行校驗。
  • 解決方法:引入 pinToDevice 對映,將所有引腳與裝置進行繫結,確保每個引腳對應唯一裝置,同時檢查連線點是否存在未定義的裝置。解析時,優先建立引腳到裝置的對映,再按順序解析連線資訊。

改進建議

1. 模組化設計

目前的輸入解析、裝置狀態更新、電路計算和輸出邏輯都集中在主方法和核心類中,耦合度較高,後續擴充套件較難。
改進方案:將主要邏輯模組化:

  • 輸入模組:專門負責解析不同格式的輸入。
  • 電路模組:處理電路拓撲和電壓計算。
  • 裝置模組:定義裝置行為及其獨立狀態更新邏輯。
  • 輸出模組:將裝置狀態格式化並統一輸出。

這樣每個模組的功能更加單一,程式碼維護成本降低。


2. 嚴格輸入驗證

現有解析邏輯對輸入的邊界情況考慮不足,例如重複連線、未定義裝置、無效引腳等。
改進方案:增加輸入驗證邏輯:

  • 確保裝置編號唯一。
  • 校驗引腳連線的合法性,避免未定義裝置或引腳重複。
  • 對無效輸入及時提示並停止解析。

3. 最佳化資料結構

當前大部分資料儲存在 HashMapList 中,雖然實現簡單,但在按順序訪問或複雜查詢時效率不高。
改進方案

  • 使用 TreeMap 按編號排序裝置。
  • 引入鄰接表結構儲存電路連線關係,便於拓撲遍歷。
  • 對並聯電路用獨立類封裝其子電路,減少對主電路的干擾。

學到的內容

1. 物件導向程式設計

兩次作業讓我深刻理解了物件導向程式設計的三大核心原則:

  • 封裝:透過 ElectricDevice 和其子類,將裝置邏輯與電路邏輯分離,裝置只需要關注自身行為。
  • 繼承:將裝置的共性抽象在基類中,子類只需實現特定功能,簡化了程式碼擴充套件。
  • 多型:透過統一介面呼叫不同裝置的狀態更新方法,降低了程式碼耦合。

2. 電路物理特性的模擬

透過程式碼實現串聯與並聯電路,我深入理解了物理規律在程式設計中的應用:

  • 串聯電路中,電流相同,電壓逐步分配;
  • 並聯電路中,電壓相等,電流按分支電阻反比分配;
  • 等效電阻公式簡化了電路的計算邏輯,為模擬複雜電路提供了理論支援。

3. 資料結構應用

兩次作業讓我認識到資料結構的重要性:

  • 使用 Map 儲存裝置資訊,快速查詢裝置狀態;
  • List 表示串聯關係,按順序更新電壓;
  • 用鄰接表儲存複雜的並聯關係,提高了資料組織的靈活性。

4. 輸入解析與錯誤處理

解析複雜輸入讓我學會了分步處理的技巧:

  • 透過正規表示式逐層提取資訊,降低解析複雜度;
  • 增加異常處理邏輯,保證解析過程穩健。

最終反思

  1. 細節決定成敗
    從正規表示式到並聯電阻公式,每個細節都可能影響程式的穩定性和正確性。這次作業讓我認識到,在複雜系統中,任何疏忽都可能引發連鎖問題。

  2. 程式碼設計的前瞻性
    第二次作業的混合電路讓我意識到,初始設計時必須考慮系統的擴充套件性。如果在第一次作業中就將電路關係抽象為模組化結構,第二次作業的實現難度會降低很多。

  3. 除錯的重要性
    兩次作業中,除錯時間遠超編碼時間。透過不斷分析測試點失敗的原因,我學會了從錯誤中尋找問題根源,並逐步完善程式。


對課程與作業的建議

  1. 增加案例分析
    希望課程中能講解更多實際開發中的案例,比如如何最佳化電路模擬的效率、如何處理複雜輸入等,幫助學生將理論與實踐更好地結合。

  2. 強化程式碼審查
    建議引入程式碼審查環節,透過同學間的互相點評,學習不同的實現思路和最佳化方法。

  3. 提供更高層次的挑戰
    在掌握基礎知識後,可以嘗試設計更復雜的任務,比如動態電路拓撲調整、並聯電路巢狀等。


透過這兩次作業,我不僅加深了對電路和程式設計的理解,還學會了如何在複雜任務中梳理思路、最佳化設計。這些經驗讓我對未來的程式設計學習充滿信心,同時也認識到,寫出“好程式碼”是一個不斷學習和進步的過程。

相關文章