---
# System prepended metadata

title: OOP物件導向 程式碼撰寫指南
tags: [Gamelab, OOP]

---

# OOP物件導向 程式碼撰寫指南

## 作業繳交

我們的作業評分採用 [Online Judge](https://hackmd.io/Qi_R6aWVQz-LH7GJLq85QQ?view) 的評分結果，但是還是**強烈建議**學生上傳到 Moodle 上

>[!Warning]
>若學生到時候沒有上傳檔案到 Moodle，則**視為放棄爭取重新批閱、調整分數的權利**

歷年的成績計算方式(正課)：
* 段考的成績大約佔比是 70~80%，合計6題，分數只有0或20分 (全對或全錯)
分數通常是0或是20的倍數，特殊題目可能會部份給分(但幾乎不會)
* 作業也是一樣，每題都只有0或100分，在根據總題目數量分布
Ex. 整學期有60題，你答對58題：`20*(58/60) = 19.34`(假定作業佔20%總分)

所以班上會有很多人的成績是相同的，例如作業成績都是滿分，考試都對 3 題之類的
* 期中考的及格率約為 30% (50人中15人 60 分以上)，期末考可能會高一點
* 期末結算大概會有 50% 的人成績是未滿60的，但至多當最後面30%的人
(總分低於60++不等於++被當，不用擔心全班都及格還要當30%的人，不會發生該情況)


>[!Tip]
上傳到 Moodle 的意義是當成績出現爭議時，助教會嘗試手動檢閱學生提交的原始碼
原始碼必須是在作業截止日期前上傳到 Moodle 的才有效

:::danger
因為助教群要從 50% 的人挑出一部分的人可以PASS：
**若且唯若**，當分數接近時，助教群會把不及格同學們的原始碼從**Moodle**下載下來，根據是否使用clang-format排版、Namespace的使用細節、Header檔案的宣告、註解撰寫...等，上課提及的細節來額外算分。這個分數影響非常小，通常是在總成績+-幾分，但會影響你在不及格的人群中是不是比較守規矩的學生。

Ex. 如果有兩個同學的成績相同，且總成績都未滿60，及格名額剩下一個的情況下：
* A有上傳原始碼到 Moodle 上，B沒有，則A及格。(**上文提及，未上傳=視同放棄爭取調分**)
* 都有上傳就看 format、namespace、header declare、註解撰寫的細節
* 分不出來助教才會寫信建議老師讓多位同學都及格
:::

### Step 1: 確保原始碼檔案已格式化

在打包解決方案前，請使用自動格式化工具格式化，格式化後的程式碼具有統一的格式，不管是在審閱還是多人協作上都非常重要。

確保Visual Studio的ClangFormat支援打開並自動執行，*每個開發環境只須設定一次*。
位於`工具` `➜` `選項`下，`文字編輯器` `➜` `C/C++` `➜` `程式碼樣式` `➜` `格式`
![image](https://hackmd.io/_uploads/rk9rOsm26.png)

針對每次作業的解決方案，
請複製助教提供的[格式化設定檔](https://gist.github.com/silent4v/25b819173f539d4037a0d4023a6613e3)內容，放置於**解決方案**資料夾中，與 `XXX.sln`同路徑，並改名為`.clang-format`

:::warning
Windows 用戶要注意，預設會隱藏副檔名，所以建立了`.clang-format`檔案，實際上名稱會是`.clang-format.txt`

參考微軟的[如何顯示副檔名](https://support.microsoft.com/zh-tw/windows/windows-%E4%B8%AD%E7%9A%84%E4%B8%80%E8%88%AC%E6%AA%94%E6%A1%88%E5%90%8D%E7%A8%B1%E5%89%AF%E6%AA%94%E5%90%8D-da4a4430-8e76-89c5-59f7-1cdbbc75cb01)，把「顯示副檔名」跟「隱藏的檔案」都勾起來，並檢查檔案系統顯示的類型是不是 `CLANG-FORMAT`
:::

![image](https://hackmd.io/_uploads/B1hSPoX2a.png)

對每個Source File與Header File進行格式化。
快速鍵`Ctrl+K` 按下後 `Ctrl+D`，即可自動格式化檔案，在開發過程中也請多加利用。
Before:
![image](https://hackmd.io/_uploads/r1B05jXhT.png)
After:
![image](https://hackmd.io/_uploads/SJG-ji7hp.png)

如果啟用鍵盤配置`Visual Studio Code`，也可以使用快捷鍵`Shift+Alt+F`。

可參考 [MSDN](https://learn.microsoft.com/zh-tw/visualstudio/ide/reference/options-text-editor-c-cpp-formatting?view=vs-2022)

### Step 2: 確保可執行檔案已被清除

專案(Project)需要建置(Build)出可執行檔(Executable)才能執行與偵錯。雖然開發的目的是得到能夠發布並執行的檔案，但是:

* 可執行檔體積通常較大。
* Debug設定下的執行檔會加入非常多的特殊指令以便偵錯，容易被誤判病毒，造成批改不便。

:::danger
不要上傳 `.obj` `.exe` `.dll` 等檔案，也不要包含 `.vs` `x86` `x64` 等資料夾
:::

所以打包上傳之前，**請清除所有建置**。
`方案總管` `➜` `解決方案XXXOOO` `➜` `右鍵選單` `➜` `清除方案`
![image](https://hackmd.io/_uploads/HJSSAim3a.png)

### Step 3: 打包整個Solution(解決方案)

請將

* 所有Project資料夾( 每一題就是一個 Project )
* `.clang-format`
* `XXX.sln`

打包成單一zip檔案，檔名為`學號_姓名.zip`。
e.g. `M11215006_蘇泓嘉.zip`

如下流程 `方案總管` `➜` `解決方案XXXOOO` `➜` `右鍵選單` `➜` `在檔案總管中開啟資料夾`
![image](https://hackmd.io/_uploads/HkaiRoXnT.png)

會看到Solution的資料夾，選取所有該上傳的檔案。
![image](https://hackmd.io/_uploads/H1Oi1nmha.png)

右鍵 `➜` 傳送到 `➜` 壓縮的(zipped)資料夾
![image](https://hackmd.io/_uploads/HJ0Cg3X2a.png)

依照命名規範命名zip檔案。

![image](https://hackmd.io/_uploads/ryTlr372a.png)

## 程式碼格式指南

:::info
隔壁組採用的[評分標準](https://hackmd.io/@5wkgZFOvS96Y7XW3W0EADA/BkET0Lgnp)，且平時就會計算排版的成績
本組助教群認為使用統一的工具進行處理才是合理的行為，也信任同學們平時就有開啟排版的功能，所以僅在決定及格名單時才會檢查並進行扣分
:::

同學們可以參考 [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) 的開發指南

![Google-Style](https://hackmd.io/_uploads/Skm87oQhT.png)

請使用 Clang-Format 進行排版，助教群提供一組 Clang-Format 設定檔，請放在專案的根目錄，檔名為`.clang-format`：

```yaml=
# see https://clang.llvm.org/docs/ClangFormatStyleOptions.html
---
BasedOnStyle: LLVM
Language: Cpp
Standard: c++14
UseTab: Never
IndentWidth: 4
ColumnLimit: 160

# Sort Headerfile
SortIncludes: true
IncludeBlocks: Merge
IncludeCategories:
  # Headers in <> without extension.
  - Regex:           '<([A-Za-z0-9\Q/-_\E])+>'
    Priority:        4
  # Headers in <> from specific external libraries.
  - Regex:           '<boost\/'
    Priority:        3
  # Headers in <> with extension.
  - Regex:           '<([A-Za-z0-9.\Q/-_\E])+>'
    Priority:        2
  # Headers in "" with extension.
  - Regex:           '"([A-Za-z0-9.\Q/-_\E])+"'
    Priority:        1


# Namepsace, Template, Function, Class Declare
AccessModifierOffset: -4
AllowShortFunctionsOnASingleLine: Empty
AllowShortLambdasOnASingleLine: Empty
AlwaysBreakTemplateDeclarations: Yes
FixNamespaceComments: true

# Alignment
BreakBeforeBraces: Custom
BraceWrapping:
  AfterCaseLabel: true
  AfterClass: false
  AfterControlStatement: MultiLine
  AfterFunction: false
  AfterNamespace: false
  BeforeCatch: false
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: Consecutive
AlignEscapedNewlines: Left
BreakConstructorInitializers: BeforeComma
IndentPPDirectives: AfterHash
PointerAlignment: Left
# If use Windows Visual Studio, DON'T Enable the following options
# AlignArrayOfStructures: Right
# PackConstructorInitializers: NextLineOnly
```

### 摘要說明

助教群針對基礎的 Coding Style 進行一些調整，主要包含

* 縮排的長度
* 預設的單行長度限制
* 標頭檔的排序順序
* 部份修飾元的縮排處理

***

除了以上的 Style 建議，我們也希望同學們額外遵守以下的限制：

* 請避免在全域直接使用 namespace
    ```cpp!=
    /* Bad Example */
    #include <iostream>
    using namespace std;
    int main() {
        cout << "Hello, World!" << endl;
        return 0;
    }
    ```
請明確引用 namespace, 或是在需要使用到的 Block 中導入
```cpp!=
/* Solution 1 */
#include <iostream>
int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

/* Solution 2 */
#include <iostream>
int main() {
    using namespace std; // import namespace here
    std::cout << "Hello, World!" << std::endl;
    return 0;
}
```
* 前置處理器相關

:::info
`#pragma once` 並不是所有編譯器都有通一的實現，因此建議使用Define Check來檢查
:::


請不要使用 `#pragma once` 來保護標頭，請使用 `#ifndef ... #endif`。

```cpp!=
#ifndef EXAMPLE_HEADER_H
#define EXAMPLE_HEADER_H

// Your codes here...

#endif
```

### 程式碼排版
使用自動化工具來對程式碼進行統一的格式排版，產出格式統一且易閱讀的程式碼。

## 程式碼註解指南

此處助教群引用 *Robert Cecil Martin（Uncle Bob)* 在其著作 *《Clean Code: A Handbook of Agile Software Craftsmanship》* 中的重要觀點

### 1. 註解不能美化糟糕的代碼

優秀的代碼應該是可讀且易於理解的，而不是通過添加註解來補救其缺陷。如果代碼需要註解來解釋它在做什麼，那麼應該重新考慮如何改進代碼的清晰度。

* 反例：使用註解去說明複雜的邏輯

```cpp=
int d; // elapsed time in days

// Calculate elapsed time in days between two time points
// Note: time1 and time2 are in format YYYYMMDD
if (((year2 - year1) * 12 + month2 - month1) * 30 + day2 - day1 > 30) {
    d = ((year2 - year1) * 12 + month2 - month1) * 30 + day2 - day1;
}
```

* 正例：通過明確的變數與函式名稱，且將大功能拆解為多個邏輯簡單、意圖明確的小功能

```cpp=
int calculateElapsedDays(Date startDate, Date endDate) {
    return endDate.toJulianDay() - startDate.toJulianDay();
}
```

> 應該盡可能使用有意義的宣告名稱，讓程式碼可以明確表達意圖

```cpp=
// 計算立方體的體積，參數是邊長
double calcVolume(int length) {
    /* Do something */
}
```

```cpp=
double calcCubeVolume(int edgeLength) {
    /* Do something */
}
```

以上兩例中，`calcVolume` 可能存在其他意思(計算什麼的體積？)，所以需要額外的註解說明
而`calcCubeVolume` 則清楚的指出要計算的是立方體的體積

### 2. 代碼應該盡量避免註解

好的代碼是不需要註解的。每當感到需要添加註解時，首先考慮是否可以通過重構代碼（例如，使用更具描述性的變量名稱、函數名稱或者更清晰的代碼結構）來使得註解變得多餘。

* 反例：在程式碼的意圖明確的情況下，進行過多解釋

```cpp=
int sum = 0; // Initialize sum to zero
sum = a + b; // Add a and b and store the result in sum
return sum; // Return the computed sum
```

* 正例：程式碼足以明確表達意圖，可以省略註解

```cpp=
int sum = a + b;
return sum;
```

### 3. 不良註解的例子

**冗余註解**：解釋了代碼已經明確說明的事情。
**誤導註解**：不精確或已過時的註解。
**義務註解**：只是因為過程規定而添加的無用註解。
**日誌式註解**：在代碼中保留大量的更新、修改歷史註解。

* 反例 1 - 日誌型註解

> 因為現代開發通常會導入CVS - 版本控制軟體，而如何寫好change log又是另外一門知識

```cpp=
/*
 * 2018-06-01: Added function (John Doe)
 * 2018-07-02: Fixed bug XYZ (Jane Smith)
 * 2018-08-03: Improved performance (John Doe)
 * 2019-01-04: Updated algorithm to include case ABC (Jane Smith)
 */
void complexFunction() {
}
```

* 反例 2 - 誤導型註解

```cpp=
/**
 * @brief 計算兩個日期的差異天數
 */
uint32_t dateDiff(Date &d1, Date &d2) {
    return abs(d1.unixTimestamp() - d2.unixTimestamp());
}

Date d1('2/22/2024, 1:03:32 AM');
Date d2('2/23/2024, 8:04:57 AM');
dateDiff(d1, d2); // 錯誤，因為計算是使用 timestamp，實際上是回傳兩個時間相差的秒數
```

* 反例3 - 冗餘註解

以一個簡單的函數為例，展示避免不必要註解的寫法：

```cpp=
// Bad example: Redundant comments
// Calculates the sum of two numbers
int addTwoNumber(int a, int b) {
    return a + b; // Returns the sum of a and b
}
```

### 4. 註解存在的正當理由

:::warning
請注意，該指南不是跟你說「盡量不要寫註解」，而是 **「註解應該是在無法使用程式明確表達意圖，或是有程式碼以外的意圖需要表達時」**

> 我們強調註解應當用於適當的情境，特別是當代碼本身無法清楚地表達開發者的意圖，或者當存在代碼本身無法涵蓋的背景信息時。
:::

下方以 [Linux Kernel Source Code](https://github.com/torvalds/linux) 為範例：

- **法律註解**：有時候需要在代碼中添加版權信息和版權聲明。

![image](https://hackmd.io/_uploads/ByC9k3m36.png)


- **提供信息**：解釋代碼無法告訴我們的信息，如某個複雜算法的背景。

![image](https://hackmd.io/_uploads/HJvVe3736.png)


- **警告**：提醒其他程序員某些行為可能有危險。

![image](https://hackmd.io/_uploads/B1z1bn7hp.png)

- **TODO 註解**：標記尚未完成或將來要改進的地方。

![image](https://hackmd.io/_uploads/H1WHMnQn6.png)

- **強調**：有時，對於那些不顯而易見的代碼行為，添加註解可以提醒程序員注意。

![image](https://hackmd.io/_uploads/SkwLz37ha.png)

## 作業常見問題

助教群會依照同學們的問題回報，將可能遇到的情況與說明更新於此。

### OJ常見問題

#### Wrong Answer可能遇到的問題 - 輸出不完整

:::info
請為每個題目考慮***測資會有多行多筆***。
顯示你的Program沒有輸出, 但答案是XXXOOO。
依照OJ的判斷邏輯，參考以下的流程。
:::

```mermaid
graph TB


subgraph "範例測資"
direction LR
1A["第一筆Excepted:123 Got:123"]-->
1B(["第二筆Excepted:456 Got:(無)"])-->
1C("然後就沒有然後了")
end

subgraph "隱藏測資1"
direction LR
e1A["第一筆Excepted:123456 Got:123456"]-->
e1B(["第二筆Excepted:456789 Got:(無)"])-->
e1C("然後就沒有然後了")
end

subgraph "隱藏測資2"
direction LR
e2A["第一筆Excepted:0011 Got:0011"]-->
e2B(["第二筆Excepted:0022 Got:(無)"])-->
e2C("然後就沒有然後了")
end

start(("Start"))-->
範例測資-->1result["顯示`Excepted:456 Got:(無)`"]-->
隱藏測資1-->e1result["顯示`Excepted:456789 Got:(無)`"]-->
隱藏測資2-->e2result["顯示`Excepted:0022 Got:(無)`"]-->
flow_end(("End \n得到3個WA"))
```
## 程式碼排版範例(使用Clang-Format)

```cpp=
#include <iostream>
// IndentPPDirectives
#ifdef WIN32
#  include <windows.h>
#endif
// SortIncludes
#include "A/MyHeader.h"
#include "B/AnotherHeader.h"
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/signal_set.hpp>
#include <boot
#include <vector>

// AlignConsecutiveMacros
#define SHORT_NAME 42
#define LONGER_NAME 0x007f
#define EVEN_LONGER_NAME (2)
#define fooo(x) (x * x)
#define baar(y, z) (y + z)

// AlignEscapedNewlines
#define PPP \
  int aaaa; \
  int b;    \
  int dddddddddd;

namespace LevelOneNamespace {
namespace LevelTwoNamespace {

struct AAAAAAAAAAAAAAAAAAAA {
  // AlignConsecutiveDeclarations
  int a;
  int bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb;
  std::string cccccccccccccccccc;
};

// AlwaysBreakTemplateDeclarations
template <typename T>
T foo() {}

// clang-format off
template <typename T> T foo(int aaaaaaaaaaaaaaaaaaaaa, int bbbbbbbbbbbbbbbbbbbbb) {}
// clang-format on

// AllowShortEnumsOnASingleLine
enum : unsigned int { AA = 0, BB } myEnum;

// SpaceBeforeInheritanceColon
class B : public E {
  private:
  // AlignArrayOfStructures
  struct AAAAAAAAAAAAAAAAAAAA test[3] = {
      {56,    23, "hello"},
      {-1, 93463, "world"},
      { 7,     5,    "!!"}
  };

  // AlignTrailingComments, AlignConsecutiveDeclarations,
  // QualifierOrder, QualifierAlignment,
  // AlignTrailingComments
  static char const* variable; // very important variable
  void* const* x = nullptr;    // not so important variable
  char const* anotherVariable; // another comment
  int a = 1;                   // another variable
  // used for this, this, and that
  int longComplicatedName = 4;
  int b = 3;

  protected:
  // AlwaysBreakAfterReturnType, QualifierAlignment
  constexpr static inline int function(int a, int b) {
    return (a + b) / 2;
  }

  // AllowShortFunctionsOnASingleLine
  static bool shortFilter(AAAAAAAAAAAAAAAAAAAA v) {
    return v.a != 4;
  }

  void empty() {}

  // IndentWrappedFunctionNames
  std::map<std::basic_string<wchar_t>, std::vector<std::pair<char, int>>> func(AAAAAAAAAAAAAAAAAAAA* v);

  public:
  // SpaceBeforeCtorInitializerColon
  explicit B()
      : a(9) {};

  // PackConstructorInitializers
  explicit B(int _a, int _b, int _c, std::vector<std::string> str)
      : a(_a), b(_b), longComplicatedName(_c), anotherVariable(str[0].c_str()) {
    // AllowShortIfStatementsOnASingleLine, SpaceBeforeParens
    if (_c)
      anotherVariable = nullptr;
    if (_a)
      anotherVariable = "baz";
    else
      anotherVariable = "bar";
  }

  // AllowAllParametersOfDeclarationOnNextLine
  // BinPackParameters
  int myFunction(int aaaaaaaaaaaaa, int bbbbbbbbbbbbbbbbbbbbbbb, int ccccccccccccc, int d, int e) {
    int myvar = aaaaaaaaaaaaa / 10;
    long anothervaw = d % 2;
    // comment
    char* msg = "Hello all";

    // AlignOperands
    myvar = bbbbbbbbbbbbbbbbbbbbbbb + ccccccccccccc + aaaaaaaaaaaaa;

    // AllowShortCaseLabelsOnASingleLine, SpaceBeforeParens
    switch (e) {
    case 1:
      return e;
    case 2:
      return 2;
    };

    // AllowShortBlocksOnASingleLine, SpaceBeforeParens
    while (true) {
    }
    while (true) {
      continue;
    }
  }

  // AlignAfterOpenBracket, BinPackParameters,
  void loooonFunctionIsVeryLongButNotAsLongAsJavaTypeNames(
      std::vector<AAAAAAAAAAAAAAAAAAAA> const& inputVector, std::map<int, std::string>* outputMap) {
    std::vector<AAAAAAAAAAAAAAAAAAAA> bar;
    std::copy_if(inputVector.begin(), inputVector.end(), std::back_inserter(bar), &shortFilter);
    // AllowShortLambdasOnASingleLine
    std::sort(inputVector.begin(), inputVector.end(), [](auto v) {
      return v.a < v.b;
    });
    std::transform(
        inputVector.begin(), inputVector.end(), std::inserter(*outputMap, outputMap->end()), [](const AAAAAAAAAAAAAAAAAAAA& element) {
          // LambdaBodyIndentation
          return std::make_pair(element.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, element.cccccccccccccccccc);
        });
  };
  int notInline(AAAAAAAAAAAAAAAAAAAA* v);
};

// AllowShortFunctionsOnASingleLine
int notInline(AAAAAAAAAAAAAAAAAAAA* v) {
  return v->a + 1;
}

} // namespace LevelTwoNamespace
} // namespace LevelOneNamespace

int main() {
  // ReflowComments
  // veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongComment
  // with plenty of information
  /* second
   * veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongComment
   * with plenty of information */

  // SortUsingDeclarations
  using std::cin;
  using std::cout;

  int aaaaaaaaaaaaaaaaaaa, bbbbbbbbbbb, ppppppppppp, eeeee;
  // AlignConsecutiveAssignments
  aaaaaaaaaaaaaaaaaaa = 6;
  bbbbbbbbbbb = 5;
  ppppppppppp = 10;
  LevelOneNamespace::LevelTwoNamespace::B b{
      1, 3, 4,
      // SpaceBeforeCpp11BracedList
      std::vector<std::string>{"aaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbb", "cccccccccccccccccccccccccccc"}
  };
  // AllowShortLoopsOnASingleLine
  for (int i = 0; i < 10; i++)
    cout << i;
  LevelOneNamespace::LevelTwoNamespace::AAAAAAAAAAAAAAAAAAAA ddddddddddddddddddddddddd{5, 5, "ff"};
  b.notInline(ddddddddddddddddddddddddd);
  // SpaceAfterCStyleCast, AllowAllArgumentsOnNextLine
  cout << (bool)b.myFunction(aaaaaaaaaaaaaaaaaaa, bbbbbbbbbbb, ppppppppppp, eeeee, 0);

  return 0;
}

```
