---
title: "寫好程式的方法 - 加入程式的脈絡"
date: 2022-12-16T00:00:00+08:00
publishDate: 2022-12-16T00:00:00Z
lastmod: 2023-06-14T19:05:52+08:00
tags: ["心得","經驗","Context"]
toc: true
permalink: "https://blog.aotoki.me/posts/2022/12/16/write-better-program-by-add-the-context/"
language: "zh-tw"
---


對脈絡（Context）比較有清晰的概念是在今年研究 Domain-Driven Design 的時候，這幾天剛好因為工作需要修正 Golang 的靜態分析警告，想到這其實也跟脈絡有些關係，因此這篇文章會來跟大家分享幾種常見的「程式脈絡」

<!--more-->

## 什麼是脈絡{#what-is-context}

脈絡，在許多技術文章中我們也會用「上下文」來稱呼，簡單來說就是「背景資訊」例如一段「加入購物車」的程式碼，我們會有著「購物車存在」這樣的前提。

要對程式的脈絡有所理解，就需要知道「資料（Data）」和「資訊（Information）」的差異，在英文字典中，資訊的解釋是「the data with context（有脈絡的資料）」這樣的定義，那麼當我告訴你「繼續」的時候因為缺乏上下文你會不清楚要「繼續什麼？」然而有了脈絡，像是「在紅燈停下來，接下來繼續前進」就會知道可能是在路上行駛中。

## 程式語言的脈絡{#context-of-programming-language}

其實程式語言的設計中，也會使用 Context 這個單字。像是作業系統中的 [Context Switching](https://zh.wikipedia.org/zh-tw/%E4%B8%8A%E4%B8%8B%E6%96%87%E4%BA%A4%E6%8F%9B) 或者程式語言的原始碼都很常看到這個單字，對於使用這些程式語言開發的工程師，最常見的應該就是函式（或者方法）本身。

```ruby
def context_a
  val = true
end

def context_b
  val = false
end

context_a
context_b
# val => nil
```

因為兩個函式有著各自的上下文，因此變數並不會互相影響，而呼叫他們的最外層（主程式）也有自己的上下文，因此無法存取到他們。

> 這也是為什麼我們需要利用參數（Parameter）來傳遞資訊，或者使用全域變數（Global Variable）來突破上下文的限制。

## 程式碼區塊的脈絡{#context-of-code-block}

除了每一個能夠成「上下文」的區段之外，一整段的程式碼也是有其脈絡存在的，這也是我在修改 Golang 靜態分析警告遇到最多的狀況。其實就是經常被爭辯的「空行」和「變數聲明」的使用，雖然在不同語言會有細微的差異，然而整體上來說還是具備其特性的。

```ruby
def pay_to(order:, credit_card:)
  payment = Payment.new(order: order)
  payment.use(:credit_card)

  service = CreditCardService.new(credit_card)
  service.authorize!(payment)
  service.capture!(payment)

  service.update(order) # => order.paid
end
```

以上面的例子，我們可以看到三個以上的脈絡資訊。

* `pay_to()` 表示了跟付款相關的邏輯，參數 `order` 和 `credit_card` 表示了可能跟電商有關，而且使用信用卡付款
* `payment.use(:credit_card)` 的段落明確的聲明「使用信用卡」
* `CreditCardService.new` 的段落描述了透過某張信用卡，對某筆付款進行扣款的情報
* `service.update(order)` 的段落則是根據付款的結果，對訂單進行更新

假如我們沒有留下空行在這幾個段落之間，那麼要理解這裡有著「三階段的小步驟」就會變得困難。另一方面，如果把 `payment` 和 `service` 都放到方法的開頭一次性的聲明，也會讓理解後續的「這個變數從何而來」的難度增加。

## 物件的脈絡{#context-of-object}

小到一行程式碼，大到一整個系統都會有脈絡存在。也因此在 [Domain-Driven Design](https://blog.aotoki.me/posts/2022/10/21/my-understand-of-domain-driven-design/) 之中定義了「領域模型」跟「分層架構」的觀念，就是要讓我們專注於「物件的脈絡」這一件事情上。

以 Value Object（值物件）來說，他是一種「資訊」也就是具備脈絡的資料，因此我們在談論「金錢」的時候，會用「幣種」加上「總數」作為一個「情報單位」

```ruby
class Money
  attr_reader :currency, :amount

  def initialize(amount, currency:)
    @amount = amount
    @currency = currency
  end

  def +(other)
    raise ArgumentError, 'invalid currency' unless other.currency == currency

    Money.new(amount + other.amount, currency: currency)
  end

  # ...
end
```

因此我們就可以很清楚的知道處理的是金錢，而且在「幣種錯誤」的狀況下無法進行換算，近一步延伸出去，還可以討論到像是 Clean Architecture 的單一職責、MVC 框架的補充 [DCI（Data Context Interaction）](https://blog.aotoki.me/posts/2022/10/28/use-data-context-interaction-natively-in-rails/)等等不同的議題，而一個系統的「架構」也是基於脈絡所構成，因此當我們在實作一個功能時沒有考慮到脈絡，就會讓整個系統變得難以理解。

## 用脈絡設計系統{#design-a-system-by-context}

假設我們用「電商網站」作為一個系統的開發目標，在沒有脈絡的狀況下我們只會將各種「電商」要素加入到這個系統中，然而有脈絡之後，可能會更清楚的知道「這是一站式網站」「使用者只會購買一種商品」等等資訊，那麼在設計的過程中就會更加清晰。

這也是實踐 Domain-Driven Design 過程中會發現的一個面向，因為我們想要畫分出 Bounded Context（上下文邊界）其實就是在整個大的系統中，繼續細分到一個「恰當的」資訊群組來一起處理這些問題。

從產品的角度 UX（使用者體驗）的使用者研究，也是在尋找「行為」上的脈絡，我們從不同的來源搜集情報並且加以彙整，最後就能找到一條符合產品需要的脈絡，進而實現更加「正確」的應用軟體。

> 這樣來看，整個程式的設計跟撰寫「小說」其實是一樣的，我們需要定義角色、場景、物件等等不同的資訊資訊，然後讓他們在舞台上互動，只不過換成軟體後變成用「物件」去定義出整個故事的要素，然後讓他動起來。

