# 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++` `➜` `程式碼樣式` `➜` `格式`

針對每次作業的解決方案,
請複製助教提供的[格式化設定檔](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`
:::

對每個Source File與Header File進行格式化。
快速鍵`Ctrl+K` 按下後 `Ctrl+D`,即可自動格式化檔案,在開發過程中也請多加利用。
Before:

After:

如果啟用鍵盤配置`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` `➜` `右鍵選單` `➜` `清除方案`

### Step 3: 打包整個Solution(解決方案)
請將
* 所有Project資料夾( 每一題就是一個 Project )
* `.clang-format`
* `XXX.sln`
打包成單一zip檔案,檔名為`學號_姓名.zip`。
e.g. `M11215006_蘇泓嘉.zip`
如下流程 `方案總管` `➜` `解決方案XXXOOO` `➜` `右鍵選單` `➜` `在檔案總管中開啟資料夾`

會看到Solution的資料夾,選取所有該上傳的檔案。

右鍵 `➜` 傳送到 `➜` 壓縮的(zipped)資料夾

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

## 程式碼格式指南
:::info
隔壁組採用的[評分標準](https://hackmd.io/@5wkgZFOvS96Y7XW3W0EADA/BkET0Lgnp),且平時就會計算排版的成績
本組助教群認為使用統一的工具進行處理才是合理的行為,也信任同學們平時就有開啟排版的功能,所以僅在決定及格名單時才會檢查並進行扣分
:::
同學們可以參考 [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) 的開發指南

請使用 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) 為範例:
- **法律註解**:有時候需要在代碼中添加版權信息和版權聲明。

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

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

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

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

## 作業常見問題
助教群會依照同學們的問題回報,將可能遇到的情況與說明更新於此。
### 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;
}
```