概念練習


概念練習是為了教授特定(程式設計)概念而設計的練習。 概念練習所教授的這些概念構成一門_教學大綱_。 如要進一步了解如何設計教學大綱,請參閱教學大綱文件。

Note

你可以從軌道的根目錄執行下列指令,快速建立一個新的概念練習的骨架:

bin/fetch-configlet
bin/configlet create --concept-exercise <slug>

如要進一步了解,請參閱configlet create 文件

中繼資料

概念練習的中繼資料定義在 config.json 檔的exercises.concept鍵中。中繼資料會定義練習的 UUID、slug 等資訊。

範例

{
  "exercises": {
    "concept": [
      {
        "uuid": "93fbc7cf-3a7e-4450-ad22-e30129c36bb9",
        "slug": "cars-assemble",
        "name": "Cars, Assemble!",
        "concepts": ["if-statements", "numbers"],
        "prerequisites": ["basics"]
      }
    ]
  }
}

檔案

每個概念練習在軌道的exercises/concept目錄下都有各自的目錄。概念練習目錄的名稱必須與 config.json 檔中定義的練習slug屬性相符。

一個概念練習有四種類型的檔案:

文件檔案

這些檔案會呈現給學生,用來協助說明這個練習。

  • .docs/introduction.md:向學生介紹這個練習所教授的概念(必填)
  • .docs/instructions.md:提供這個練習的指示(必填)
  • .docs/hints.md:提供提示,幫助學生在練習中突破卡關(必填)

中繼資料檔案

這些檔案_不會_呈現給學生,而是用來定義練習的中繼資料。

  • .meta/config.json:包含這個練習的中繼資訊(必填)
  • .meta/design.md:說明這個練習的設計(必填)

解法檔案

這些檔案說明這個練習的各種解法。

  • .approaches/introduction.md:這個練習最常見解法的簡介(選填)
  • .approaches/config.json:這些解法的中繼資料(選填)
  • .approaches/<approach-slug>/content.md:這個解法的說明(選填)
  • .approaches/<approach-slug>/snippet.txt:展示這個解法的片段(選填)

文章檔案

這些檔案說明這個練習的文章。

  • .articles/config.json:這些文章的中繼資料(選填)
  • .articles/<article-slug>/content.md:這篇文章的說明(選填)
  • .articles/<article-slug>/snippet.md:展示這篇文章的片段(選填)

練習檔案

特定語言的檔案,例如實作檔與測試檔。這些檔案的名稱依軌道而定。

  • 測試套件:驗證解答是否正確(必填)
  • 骨架實作:提供學生一個起點(必填)
  • 範例實作:提供能通過所有測試的慣用實作(必填)
  • 其他檔案:確保測試能夠執行(選填)

範例

exercises
└── concept
    └── cars-assemble
        ├── .approaches
        |   ├── for-loop
        |   |   ├── content.md
        |   |   └── snippet.txt
        |   ├── config.json
        |   └── introduction.md
        ├── .articles
        |   ├── performance
        |   |   ├── content.md
        |   |   └── snippet.md
        |   └── config.json
        ├── .docs
        |   ├── introduction.md
        |   ├── instructions.md
        |   └── hints.md
        ├── .meta
        |   ├── config.json        
        |   ├── design.md
        |   └── Exemplar.cs(範例實作)
        ├── CarsAssemble.cs(骨架實作)
        └── CarsAssemblyTests.cs(測試)

最小可接受規格

對於新的練習,我們偏好採用「樂觀合併」的做法,讓軌道能以「進行中」的狀態開發練習。 能通過 configlet 檢查並讓你合併的最小有效狀態如下:

  • 軌道config.json中的有效項目,且status設為wip。
  • 有效的.meta/config.json檔
  • 以下檔案必須存在,但可以為空:
    • .docs/introduction.md
    • .docs/instructions.md
    • .docs/hints.md
    • 骨架實作
    • 測試檔

檔案:.docs/introduction.md

用途: 向學生介紹這個練習所教授的概念。

是否必填: 必填

  • 提供的資訊應該讓學生有恰到好處的背景知識,能自行想出解答。
  • 只應提供理解概念基礎與解出練習所需的資訊。額外的資訊應留給該概念的about.md文件。
  • 連結應盡量少用,最好完全不用。雖然對於像遞迴這類複雜主題,提供一個解說連結可能有用,但對大多數概念來說,連結提供的資訊往往超出所需,因此目標應該是直接在文中簡潔地說明。
  • 應使用正確的專業術語,讓學生能輕鬆搜尋更多資訊。
  • 程式碼範例只應用來介紹新語法(學生不應該需要上網搜尋語法範例)。其他情況下,請提供說明或連結,而不要提供程式碼。

舉例來說,「strings」練習的簡介可能會把字串描述為「一連串的 Unicode 字元」或「一連串的位元組」,告訴使用者如何建立字串,並說明字串有一些方法可以用來操作它。除非學生需要理解更細微的細節才能解出練習,否則這類簡短的說明(再加上一個語法範例)應該就足以讓學生解出練習。

範例

# Introduction

There are two primary ways to assign objects to names in Ruby - using variables or constants. Variables are always written in snake case. A variable can reference different objects over its lifetime. For example, `my_first_variable` can be defined and redefined many times using the `=` operator:

```ruby
my_first_variable = 1
my_first_variable = "Some string"
my_first_variable = SomeComplexObject.new
```

檔案:.docs/introduction.md.tpl

用途: 用來產生introduction.md檔的範本。

是否必填: 選填

introduction.md文件會向學生介紹這個練習的概念。每個概念也有它_自己_的introduction.md文件,但這份文件不會在練習情境之外顯示。

如果概念的簡介需要原文照登在練習的簡介中,可以使用introduction.md.tpl檔。這個檔案讓你能透過佔位符來引用概念的簡介:%{concept:<concept-slug>}。

configlet可以從範本檔產生introduction.md檔。產生的檔案會把概念佔位符替換成該概念的introduction內容。

Exercism 網站只知道introduction.md這份文件。使用範本檔時,產生introduction.md是軌道的責任。

軌道可以針對每個練習決定是否使用範本。在某些情況下,原文照登概念的簡介可能不是最好的做法。請永遠選擇能為學生帶來最佳學習體驗的方式。

範例

# Introduction

%{concept:variables}

檔案:.docs/instructions.md

用途: 提供這個練習的指示。

是否必填: 必填

這個檔案分成兩個部分。

  1. 第一部分說明這個練習的「故事」或「主題」。這部分通常不應包含程式碼範例。
  2. 第二部分以一或多個任務的形式,清楚說明學生需要做什麼。

每個任務都必須符合下列標準:

  • 以數字開頭的第二層標題開始(例如## 1. Do X、## 2. Do Y)。
  • 標題應該描述要實作_什麼_,而不是_如何_實作(例如## 1. Check if an appointment has already passed)。
  • 說明學生需要定義/實作哪個函式/方法(例如Implement method X(...) that takes an A and returns a Z),
  • 用程式碼提供該函式的使用範例。這些範例應該與測試中提供的範例不同。

我們非常重視讓 Exercism 的內容對所有人都是安全的,因此在判斷故事是否合適時,經常寧願過度謹慎。雖然我們對合併的內容很謹慎,但我們也知道,要察覺什麼內容可能被視為有問題並不容易,所以我們永遠會假設你是出於善意,並且會在審查時盡量以不對立的方式找出任何問題。如果你想和我們一起檢查某個故事,請標註 @exercism/leadership,我們會一起來看看。以下是一些指引:

  • 請盡量確保故事是友善的,而且每個人都能理解。如果故事包含只有內行人才懂的玩笑或地區俚語,請試著想一些替代說法。
  • 請盡量撰寫對所有人都包容的範例。例如,可以考慮使用來自其他文化的名字,以及混合的性別。
  • 問問自己,你是否認識任何會因為這個故事而感到被冒犯的人。如果有,請考慮修改以避免這種情況。

範例

# Instructions

In this exercise you're going to write some code to help you cook a brilliant lasagna from your favorite cooking book.

## 1. Calculate the remaining oven time in minutes

Define the `Lasagna#remaining_minutes_in_oven` method that takes the actual minutes the lasagna has been in the oven as a parameter and returns how many minutes the lasagna still has to remain in the oven, based on the expected oven time in minutes from the previous task.

```ruby
lasagna = Lasagna.new
lasagna.remaining_minutes_in_oven(30)
# => 10
```

檔案:.docs/hints.md

用途: 提供提示,幫助學生在練習中突破卡關。

是否必填: 必填

  • 如果學生卡住了,我們會讓他們點擊按鈕來要求提示,按鈕會顯示檔案中相關的部分。
  • 提示應該以項目符號列在標題下方。
  • 提示應該足以讓幾乎所有學生都能突破卡關。
  • 提示不應該直接寫出解答,而應該指向說明解答的資源(例如連結到要使用之函式的文件)。
  • 提示可以使用程式碼範例來解釋概念,但不能用來概述解答。例如在陣列練習中,提示可以展示某個陣列函式運作方式的片段,但不能以能直接複製貼上到解答中的方式呈現。
  • 關於練習的一般提示,可以放在## General標題下的 Markdown 清單中。
  • 針對特定任務的提示,應該放在與instructions.md中任務標題相符的標題下方,以 Markdown 清單呈現(例如## 2. Do Y)。
  • 如果沒有一般提示,或沒有特定任務的提示,就應該省略該標題。每個標題後面都必須接著一個 Markdown 清單。
  • 請優先提供特定任務的提示,而非一般提示,因為特定任務的提示比一般提示更有可能幫助學生突破卡關。
  • 任務標題應該描述任務的_什麼_,而不是_如何_。
  • 任務標題應該使用一般的句子大小寫(例如## 2. Check if a book can be borrowed)。
  • 任務應該明確說明要實作哪個方法/函式/型別,以及它預期的值(例如Implement the 'canBorrowBook' function to check if a book can be borrowed. The function takes a book as its parameter and returns `true` if the book has not already been borrowed; otherwise, return `false`)。

查看提示不會是「建議」的路徑,除非學生沒有它就無法繼續,否則我們會(委婉地)勸阻使用。因此,值得考慮的是,閱讀提示的學生可能會有點困惑、不知所措,甚至感到挫折。

範例

# Hints

## General

- You need to define a [constant][constant] which should contain the [integer][integers] value specified in the recipe.

## 1. Calculate the remaining oven time in minutes

- You need to define a [method][methods] with a single parameter for the actual time so far.

[constants]: https://www.rubyguides.com/2017/07/ruby-constants/
[integers]: https://ruby-doc.org/core-2.7.0/Integer.html
[methods]: https://launchschool.com/books/ruby/read/methods

檔案:.meta/design.md

用途: 說明這個練習的設計。

是否必填: 必填

這個檔案包含練習設計的相關資訊,例如它的目標、教學目標、不教什麼等等。這些資訊可以從練習對應的 GitHub issue 中擷取。

它的存在是為了讓未來的維護者或貢獻者了解練習的範圍與限制,以避免練習隨著時間自然變得越來越複雜。

範例

# Design

## Goal

The goal of this exercise is to teach the student the basics of programming in Ruby.

## Learning objectives

- Know what a variable is.
- Know how to define a variable.
- Know how to update a variable.

## Out of scope

- Memory and performance characteristics.
- Method overloads.

## Concepts

The Concepts this exercise unlocks are:

- `basics`: know what a variable is; know how to define a variable; know how to update a variable.

## Prerequisites

There are no prerequisites.

檔案:.meta/config.json

用途: 包含這個練習的中繼資訊。

是否必填: 必填

這個檔案包含練習的中繼資訊:

  • authors:練習作者的 GitHub 使用者名稱(必填)
    • 如果審查者的審查大幅改變了這個練習(到了讓人覺得「你們是一起完成的」程度),也要一併列入
  • contributors:練習貢獻者的 GitHub 使用者名稱(選填)
    • 如果審查者的審查有意義、可付諸行動,或已被採用,也要一併列入。
  • forked_from:這個練習是從哪些練習分支出來的(如果練習是分支來的則必填)
  • files:這個練習所用檔案的位置,相對於練習目錄(必填)
    • solution:骨架實作檔(必填)
    • test:測試檔(必填)
    • exemplar:範例實作檔(必填)
    • editor:在編輯器中以唯讀方式顯示的其他檔案(選填)
    • invalidator:變更後會讓解答變成過期的檔案(選填)
  • language_versions:語言版本需求(選填)
  • blurb:這個練習的簡短描述。長度必須 <= 350。_不_支援 Markdown(必填)
  • source:這個練習所根據的來源(選填)
  • source_url:這個練習所根據來源的 URL(選填)
  • representer:與 representer 如何處理這個檔案相關的中繼資訊(選填)
    • version:要用於這個練習的 representer 版本整數(若父鍵存在則必填)
  • icon:圖示的 slug(請參閱完整圖示清單)。如果未指定,會使用練習的 slug(選填)
  • custom:任何練習專屬的非標準資料。可用來針對個別練習自訂軌道工具的行為(選填)

如果某人同時是作者_和_貢獻者,只需將該人列為作者。

最小範例

{
  "authors": ["FSharpForever"],
  "files": {
    "solution": ["Lasagna.fs"],
    "test": ["LasagnaTests.fs"],
    "exemplar": [".meta/Exemplar.fs"]
  },
  "blurb": "Learn the basics of F# by cooking Lucian's Luscious Lasagna"
}

完整範例

假設使用者FSharpForever為 F# 軌道寫了一個叫做log-levels的練習。PythonProfessor把這個練習改編給 Python 軌道。後來,使用者GladToHelp改進了這個練習。

{
  "authors": ["PythonProfessor"],
  "contributors": ["GladToHelp"],
  "files": {
    "solution": ["log_levels.py"],
    "test": ["log_levels_test.py"],
    "exemplar": [".meta/exemplar.py"],
    "editor": ["test_helper.py"]
  },
  "forked_from": ["fsharp/log-levels"],
  "language_versions": ">=3.7",
  "blurb": "Learn how to work with strings by processing log lines.",
  "source": "Wikipedia",
  "source_url": "https://en.wikipedia.org/wiki/Log_file",
  "representer": {
    "version": 2
  },
  "icon": "logs",
  "custom": {
    "parallel": true
  }
}

請注意:

  • 作者與貢獻者的順序不重要,也沒有任何意義。
  • 如果你要分支一個練習,請不要標註原作者或貢獻者。只要確認forked_from正確即可。
  • 雖然不常見,但_確實_可以從多個練習分支。
  • language_versions是自由格式的字串,軌道可以自行決定如何使用與解讀。

檔案:.approaches/introduction.md

用途: 這個練習最常見解法的簡介

是否必填: 選填

這個檔案說明這個練習最常見的解法。 關於這個檔案應該包含什麼,請參閱文件。

範例

# Introduction

The key to this exercise is to deal with C# strings being immutable, which means that a `string`'s value cannot be changed.
Therefore, to reverse a string you'll need to create a _new_ `string`.

## Using LINQ

```csharp
public static string Reverse(string input)
{
    return new string(input.Reverse().ToArray());
}
```

For more information, check the [LINQ approach][approach-linq].

## Which approach to use?

If readability is your primary concern (and it usually should be), the LINQ-based approach is hard to beat.

檔案:.approaches/config.json

用途: 這些解法的中繼資料

是否必填: 選填(當有解法簡介或解法存在時則必填)

這個檔案包含這個練習各種解法的中繼資訊:

  • introduction:練習解法簡介作者的 GitHub 使用者名稱(選填)

    • authors:練習解法簡介作者的 GitHub 使用者名稱(必填)
      • 如果審查者的審查大幅改變了練習的解法簡介(到了讓人覺得「你們是一起完成的」程度),也要一併列入
    • contributors:練習解法簡介貢獻者的 GitHub 使用者名稱(選填)
      • 如果審查者的審查有意義、可付諸行動,或已被採用,也要一併列入。
  • approaches:列出詳細解法的陣列(選填)

    • uuid:唯一識別該解法的 V4 UUID。這個 UUID 在軌道內以及所有軌道之間都必須是唯一的,且永遠不能改變
    • slug:解法的 slug,是一律小寫的 kebab-case 字串。這個 slug 在軌道內的所有解法 slug 之間必須是唯一的。長度必須 <= 255。
    • title:解法的標題。長度必須 <= 255。
    • blurb:這個解法的簡短描述。長度必須 <= 350。_不_支援 Markdown(必填)
    • authors:練習解法作者的 GitHub 使用者名稱(必填)
      • 如果審查者的審查大幅改變了練習的解法(到了讓人覺得「你們是一起完成的」程度),也要一併列入
    • contributors:練習解法貢獻者的 GitHub 使用者名稱(選填)
      • 如果審查者的審查有意義、可付諸行動,或已被採用,也要一併列入。
    • tags:指定提交內容在什麼條件下會連結到某個解法。(選填)
      • all:提交上必須全部具備的標籤陣列(選填,除非any沒有任何元素)
      • any:提交上至少必須具備其中一個的標籤陣列(選填,除非all沒有任何元素)
      • not:提交上不得具備任何一個標籤(選填)

範例

{
  "introduction": {
    "authors": ["erikschierboom"]
  },
  "approaches": [
    {
      "uuid": "448fb2b4-18ab-4e55-aa54-ad4ed6d5f7f6",
      "slug": "span",
      "title": "Use Span<T>",
      "blurb": "Use Span<T> to efficiently reverse a string.",
      "authors": ["erikschierboom"]
    }
  ]
}

檔案:.approaches/<approach-slug>/content.md

用途: 該解法的詳細說明

是否必填: 選填(有解法時則必填)

這個檔案包含該解法的詳細說明。 關於這個檔案應該包含什麼,請參閱文件。

範例

# Span

```csharp
Span<char> chars = stackalloc char[input.Length];
for (var i = 0; i < input.Length; i++)
{
    chars[input.Length - 1 - i] = input[i];
}
return new string(chars);
```

This `Span<T>` approach uses a `for` loop.

檔案:.approaches/<approach-slug>/snippet.txt

用途: 展示該解法的片段

是否必填: 選填(有解法時則必填)

這個檔案包含一小段展示該解法的片段。 這段片段會顯示在練習的深入探索頁面。

它的行數必須 <= 8。

關於這個檔案應該包含什麼,請參閱文件。

範例

Span<char> chars = stackalloc char[input.Length];
for (var i = 0; i < input.Length; i++)
{
    chars[input.Length - 1 - i] = input[i];
}
return new string(chars);

檔案:.article/config.json

用途: 這些文章的中繼資料

是否必填: 選填(有文章存在時則必填)

這個檔案包含這個練習各種文章的中繼資訊:

  • articles:列出詳細文章的陣列(選填)
    • uuid:唯一識別該文章的 V4 UUID。這個 UUID 在軌道內以及所有軌道之間都必須是唯一的,且永遠不能改變
    • slug:文章的 slug,是一律小寫的 kebab-case 字串。這個 slug 在軌道內的所有文章 slug 之間必須是唯一的。長度必須 <= 255。
    • title:文章的標題。長度必須 <= 255。
    • blurb:這篇文章的簡短描述。長度必須 <= 350。_不_支援 Markdown(必填)
    • authors:練習文章作者的 GitHub 使用者名稱(必填)
      • 如果審查者的審查大幅改變了練習的文章(到了讓人覺得「你們是一起完成的」程度),也要一併列入
    • contributors:練習文章貢獻者的 GitHub 使用者名稱(選填)
      • 如果審查者的審查有意義、可付諸行動,或已被採用,也要一併列入。

範例

{
  "articles": [
    {
      "uuid": "6db71962-62d5-448b-a980-c20ae41013ed",
      "slug": "performance",
      "title": "Optimizing performance",
      "blurb": "Explore how to most efficiently reverse a string and what the trade-offs are.",
      "authors": ["erikschierboom"]
    }
  ]
}

檔案:.articles/<article-slug>/content.md

用途: 該解法的詳細說明

是否必填: 選填(有解法時則必填)

這個檔案包含該解法的詳細說明。 關於這個檔案應該包含什麼,請參閱文件。

範例

# Performance

In this document, we'll find out which approach is the most performant one.

## Benchmark results

| Method |      Mean |     Error |    StdDev |    Median | Allocated |
| -----: | --------: | --------: | --------: | --------: | --------: |
|   Linq | 29.133 ns | 0.5865 ns | 0.5486 ns | 28.984 ns |      80 B |
|  Array |  4.806 ns | 0.4999 ns | 1.4739 ns |  3.967 ns |         - |

檔案:.articles/<article-slug>/snippet.txt

用途: 展示該解法的片段

是否必填: 選填(有文章時則必填)

這個檔案包含一小段展示該文章的片段。 這段片段會顯示在練習的深入探索頁面。

它的行數必須 <= 8。

關於這個檔案應該包含什麼,請參閱文件。

範例

| Method |      Mean | Allocated |
| -----: | --------: | --------: |
|   Linq | 29.133 ns |      80 B |
|  Array |  4.806 ns |         - |

檔案:骨架實作

用途: 提供學生一個起點。

是否必填: 必填

  • 設計骨架時,要讓學生知道該在哪裡加入程式碼。
  • 為練習中未介紹到的任何語法定義骨架。對大多數練習來說,這表示要定義骨架函式/方法。
  • 對編譯式語言,請考慮讓程式碼可以編譯,因為對剛接觸這門語言的學生來說,編譯器訊息有時會難以理解。
  • 程式碼應該盡可能簡單。
  • 只使用這個練習或其先修項目(以及先修項目的先修項目,依此類推)所介紹的語言功能。
  • 學生在瀏覽器中撰寫程式碼時會看到骨架檔,使用 CLI 時則會下載到學生的檔案系統。
  • 骨架實作檔的相對路徑必須指定在.meta/config.json檔的"files.solution"鍵中。

範例

class Lasagna
  def remaining_minutes_in_oven(actual_minutes_in_oven)
    raise NotImplementedError, 'Please implement the Lasagna#remaining_minutes_in_oven method'
  end

  def preparation_time_in_minutes(layers)
    raise NotImplementedError, 'Please implement the Lasagna#preparation_time_in_minutes method'
  end
end

檔案:測試

用途: 驗證解答是否正確。

是否必填: 必填

  • 測試不應該使用instructions.md檔中的範例。
  • 程式碼應該盡可能簡單。
  • 只使用這個練習的先修項目(以及先修項目的先修項目,依此類推)所介紹的語言功能。
  • 學生在瀏覽器中撰寫程式碼時_不會_看到測試檔,但使用 CLI 時_會_下載到學生的檔案系統。
  • 測試檔的相對路徑必須指定在.meta/config.json檔的"files.test"鍵中。

範例

require 'minitest/autorun'
require_relative 'lasagna'

class LasagnaTest < Minitest::Test
  def test_remaining_minutes_in_oven
    assert_equal 15, Lasagna.new.remaining_minutes_in_oven(25)
  end

  def test_preparation_time_in_minutes_with_one_layer
    assert_equal 2, Lasagna.new.preparation_time_in_minutes(1)
  end

  def test_preparation_time_in_minutes_with_multiple_layers
    assert_equal 8, Lasagna.new.preparation_time_in_minutes(4)
  end
end

檔案:範例實作

用途: 提供學生應該努力的目標實作。

是否必填: 必填

  • 這個實作是我們希望學生努力達成的目標程式碼。
  • 導師在撰寫回饋時,會看到這段程式碼作為「目標」
  • 這個實作只應該使用這個練習或其先修項目(以及先修項目的先修項目,依此類推)所介紹的語言功能。
  • 學生在瀏覽器中撰寫程式碼時_不會_看到範例檔,使用 CLI 時也_不會_下載到學生的檔案系統。
  • 導師在對解答或 representation 留言時,會看到範例檔。
  • 範例實作檔的相對路徑必須指定在.meta/config.json檔的"files.exemplar"鍵中。

範例

class Lasagna
  EXPECTED_MINUTES_IN_OVEN = 40
  PREPARATION_MINUTES_PER_LAYER = 2

  def remaining_minutes_in_oven(actual_minutes_in_oven)
    EXPECTED_MINUTES_IN_OVEN - actual_minutes_in_oven
  end

  def preparation_time_in_minutes(layers)
    layers * PREPARATION_MINUTES_PER_LAYER
  end
end

檔案:其他檔案

用途: 確保測試能夠執行。

是否必填: 當預設檔案不足以執行測試時則必填

有些語言需要其他檔案才能執行測試。例如 C# 的專案檔和 Node 的package.json檔,沒有它們就無法執行測試。

共用檔案

有些檔案並非個別練習專屬,而是適用於_所有_練習。如需更多資訊,請參閱文件。

命名

概念練習應該依照它的故事/主題來命名,而_不是_依照它的概念。

好的名稱範例:

  • Tim from Marketing
  • Lucian's Luscious Lasagna
  • Calculator Conundrum

不允許的名稱:

  • Booleans:使用了概念名稱,而不是故事名稱
  • Exercise #1:練習不是故事/主題

分支練習時如果沒有大幅變更,請盡可能使用原本的名稱。

Slug

每個練習也有一個_slug_,它是依照下列規則將練習名稱正規化後的版本:

  1. 使用小寫。
  2. 使用 kebab-case。
  3. 使用拉丁字母與數字字元,以及連字號(正規表示式:[a-z0-9-]+)
  4. 優先使用拼寫出來的數字,而非阿拉伯數字,除非有特定理由偏好阿拉伯數字(例如偏好two-fer而非2-fer)

好的 slug 範例:

  • tim-from-marketing
  • lucians-luscious-lasagna
  • calculator-conundrum

不允許的 slug:

  • TIM-FROM-MARKETING:未使用小寫(也就是tim-from-marketing)
  • TimFromMarketing:未使用 kebab-case(也就是tim-from-marketing)
  • floating-point-numbers:使用了概念名稱,而不是故事名稱

呈現方式

使用瀏覽器內編輯器與使用 CLI 時,練習文件的呈現方式有所不同。如需更多資訊,請參閱這份文件。

圖示

每個練習都有一個搭配的圖示。 根據預設,顯示的圖示是名稱與練習 slug 相符的那一個。 你可以藉由在練習的.meta/config.json檔中指定icon屬性來覆寫。

如果你要分支現有的練習,那個練習可能已經有圖示了。 如果沒有,請在 website-icons 儲存庫開一個 issue。