2022-6-21
耳にはするが意味がわかってなかった用語をまとめる
耳にはするが意味をふんわりとは知っているがちゃんと理解していない用語がよくある。随時その用語と詳細を追加していく。
null安全
Null Pointer Exception
を起こさないようにする仕組みのこと。
TypeScriptで言うところの以下のようなコード
const hoge: string[] | undefined = undefined
if (hoge.length === 0) someFunc() // <= コンパイルエラー
上記のコードはTypeScriptだと hoge.length
をしている時点で string[]
であることが確定していないのでコンパイルエラーとなる。これを回避するには以下のようにする必要がある。
const hoge: string[] | undefined = undefined
if (hoge !== undefined) {
if (hoge.length === 0) someFunc() // <= コンパイルOK
}
TypeScriptを使っていると割と当然のように思える仕組みだが、このような仕組みがない言語だと List
型の変数に当然のように null
が入っているケースがあり、nullチェックがないせいでランタイムエラーが発生するというのがよくあったみたい。
参照透過性
関数の性質。引数で結果が一意に定まること。
以下の関数は、引数がそもそも無いのに呼び出したタイミングによって結果が変わってしまうので参照透過性が無い。
const getCurrentUnixTime = () => {
return Date.now()
}
参照透過性がないと、関数のテストが非常にやりにくくなる。今回の場合だと Date
のモックをする必要がある。
以下のようにコードを修正すると、改善する。
const getUnixTime = (date: Date) => {
return date.getTime()
}
const currentDate = new Date()
const currentUnixTime = getUnixTime(currentDate)
- 現在のUnixTimeを取得する
という処理を
- 現在時刻を取得する
- 時刻をUnixTimeに変換する
という参照透過に出来る処理と出来ない処理に分割することで、テストしやすい部分を作り出せた。
現在時刻を取得する箇所に関してはテストしにくいのはどうしようもない。
純粋関数
参照透過かつ副作用を持たない関数のこと。
参照透過はあくまで返り値が一意に定まるだけで、副作用の有無は問わない。なので「参照透過だが副作用がある」はありうる。
例えばJavaScriptの sort
関数は元となる配列に破壊的変更を加えるが、ソート後の配列を返り値として返しており、そしてそれは引数により一意に定まるので参照透過である。
なので「参照透過な関数」って言葉が出てくる時、正しくは「純粋関数」を指していることが結構ありそう。
ちなみに純粋関数には、結果をキャッシュすることも出来るというメリットもあったりする。
冪等性
現在の状態に関わらず、何回実行しても実行後の状態が同じになること。
例えば、サーバー監視の有効/無効を制御する toggleServerMonitoring
という処理があるとする。これは現在の状態に応じて実行後の状態は異なる。これは冪等性が「無い」。
冪等性が無いと困ることがある。一例として何らかの理由で同じ処理が複数回実行された際に予期せぬ結果になることがある。例えばHttpリクエストに失敗してリトライしたら、2回処理が実行された等。
今回のケースだと、 toggleServerMonitoring
ではなく enableServerMonitoring
と disableServerMonitoring
の処理を用意しそれぞれ必要な方を叩けばいい。サーバーの監視を有効化したい際は enableServerMonitoring
を実行し、そうすればリトライにより複数回実行されても「サーバーの監視が有効化されている」という状態になることは変わらない。
冪等性とは「同じ操作を何度繰り返しても、同じ結果が得られる性質」のこと
第一級関数・高階関数・コールバック関数
第一級関数
関数を第一級オブジェクトとして扱うことのできるプログラミング言語の性質、またはそのような関数のこと
第一級関数
たとえば生成、代入、演算、(引数・戻り値としての)受け渡しといったその言語における基本的な操作を制限なしに使用できる対象のことである。
第一級オブジェクト
まとめると、「生成・代入・演算・引数戻り値としての受け渡しといった基本的な操作を制限なしに使用できる関数のこと」。関数が数値とかと同様に操作出来るのであれば第一級関数なので、JavaScriptの関数は第一級関数。C言語は関数ポインタにすれば基本的な操作が可能だが、関数ポインタという物を経由する必要があるからか第一級関数とみなされてはいない模様。
高階関数・コールバック関数
高階関数は「引数または戻り値に関数がある関数」のこと。コールバック関数は「関数の引数として指定された関数」のこと。
const highOrderFunction = (callbackFunction: () => void) => {
// なんらかの処理
callbackFunction()
}
TypeScriptのコードで言うと、上の highOrderFunction
が高階関数で callbackFunction
がコールバック関数。
メタプログラミング
なんかプログラムでプログラムを弄ること。
言葉では説明しづらいので具体例を挙げる。
const repository = {
async fetchHtml() {
const response = await fetch('https://example.com')
const text = response.text()
return text
}
}
const main = async () => {
const result = await repository.fetchHtml()
console.log(result)
}
main()
上のような通信が伴う処理がある時、main関数をテストしたいなってなったら困る。テストのための通信を受けるサーバーを用意したりなんやかんやで。
なので、テストの時には実際に通信を走らせたくない。素直にやるのであればDIしてテスト時は実際に通信が走らないMockRepository的なのを呼び出せばいいけど、以下のような書き方もできる。
const repository = {
async fetchHtml() {
const response = await fetch('https://example.com')
const text = response.text()
return text
}
}
Object.defineProperty(repository, 'fetchHtml', {
value: async () => {
return 'mocked html'
}
})
const main = async () => {
const result = await repository.fetchHtml()
console.log(result)
}
main()
上は Object.defineProperty
で関数の動作を上書きしている。この方法であればinterfaceを大量に作ったりとかしなくてもテストに関して解決する。Jestの jest.mock
はここらへんを使ってうまいことなんやかんやしてるのだと思う。
こんな風にランタイムに記述されたソースの動作を無理やり上書きしたりする感じのやつをメタプログラミングって言うんだと思う(ふんわりとした理解)。
腐敗防止層
理想の構造にしたいが現実的に無理な時に、腐敗防止層という層を作って理想的じゃない部分はそこに押し込める。インタフェースだけ理想の形にして、腐敗防止層内で理想の構造と現実の構造を変換する、的なやつ、多分。
個人的によくやるのが、フロントエンド的にはこういうAPIが欲しいが、諸々の事情でそのAPIが用意出来ない場合に、腐敗防止層を作って理想的なAPIが存在しない悪影響を最小限に留めるようにしたりする。(サードパーティのAPIを利用して何か作るときによくやる)
具体例
例えば、あるAPI [GET] /user/:id
では性別を、数値で返していて、
1: 男性
2: 女性
3: その他
みたいなマッピングになっているとする。
また、別のAPI [GET] /company/:id/employee
では性別を文字列で返していて、
male: 男性
female: 女性
other: その他
みたいなマッピングになっている。
本来ならバックエンド側を改修すべきだが、バックエンドの人手が足りないとかそこらへんの事情で対応無理でーす、な状況。レガシーサービスのフロントエンドのリプレースであり得る。
まぁ、割とこのレベルの状況な時点で相当やばいんだけど、それはさておき。
ここは本来API側で解決すべき内容なので、UI側には悪影響を与えたくない。
なので、Repository層を腐敗防止層としてレスポンスを変換するみたいな事をする。
例えば、UI側での性別の型が、
type Gender = 'male' | 'female' | 'other'
だとすると、UI側の型と異なっており変換が必要な UserRepository
では
const convertGender = (gender: number) => {
switch (gender) {
case 0:
return 'male'
case 1:
return 'female'
case 2:
return 'other'
}
throw new Error(`想定外のgender: ${gender}`)
}
const fetchUser = (id: string): Promise<User> => {
const response = axios.get('some_api_url')
return {
...response.data,
gender: convertGender(response.data.gender)
}
}
みたいな感じで変換して返却する。
こうしておけばUI側の実装はAPIが理想的じゃない事を気にせず実装出来てシンプルになる。
DTO/DAO
- DTO
- データの受け渡しのための入れ物となるオブジェクト
- DAO
- データストアへのアクセス手段を提供するオブジェクト
- Repositoryとほぼ同じだが、Repositoryより抽象度が低い
- データストアへのアクセス手段を提供するオブジェクト
コード例(TypeScript)
type UserDTO = {
id: string
name: string
age: number
}
interface UserDAO {
list(): Promise<UserDTO[]>
find(id: string): Promise<UserDTO[]>
...
}
こんな感じのやつだと思う、意識してないだけでめっちゃ使う(特にDTO)。
DTO見てると「なんでこんなものに大仰なパターン名がつけられてるんだ?」って思ったりするけど、JavaScriptと比べてJavaはオブジェクトの作成が面倒(クラス定義してgetter/setter定義して〜ってやらないといけない)なので、手間がある分特別感があって名付けされたのかも(適当)。
ちなみに、DTOのJavaでの実装例見てると「なんでわざわざprivateにしてgetter/setter定義してるんだ?この利用法ならpublicでいいのでは?」と思ってたら同様のことを思っている人がいた。さらに言うとデータ受け渡しのオブジェクトなんだからgetterだけ用意するreadonlyな形がより良い気もする。
DAOは上でRepositoryより抽象度が低いと書いたが、DAOの解説記事によっては「データベースの変更に対応できるようインターフェースを定義して〜」とかもあったりするので解釈がマチマチなのかもしれない。たまに抽象度が低いRepositoryを見たりする(自分もやってしまったりする)のでまぁそういうもんか。
参考
やはりお前たちのRepositoryは間違っている - Qiita
構造的部分型
派生型の方式。派生型の方式としては構造的部分型(TypeScript,Goとか)と公称型(Java,C++)があるみたい。
違い
派生型は原則としてリスコフの置換原則っていうのを満たす必要がある。
リスコフの置換原則はざっくり「型Aの派生型Bが存在する場合、AはBで置換できる」っていうやつ。
リスコフの置換原則(LSP)をしっかり理解する - Qiita
公称型と構造的部分型の違いは、公称型では派生型同士は置換できないという点。
具体的に言うと、
// 公称型(Java)
class A {
void someFunc() {
//~
}
}
class B extends A {
void someFunc() {
//~
}
}
class C extends A {
void someFunc() {
//~
}
}
// 問題なし
A var1 = new B()
A var2 = new C()
// コンパイルエラー
B var3 = new C()
C var4 = new B()
// 構造的部分型(TypeScript)
class A {
someFunc() {
//~
}
}
class B extends A {
someFunc() {
//~
}
}
class C extends A {
someFunc() {
//~
}
}
// 問題なし
const var1: A = new B()
const var2: A = new C()
// 問題なし
const var3: B = new C()
const var4: C = new B()
こんな感じ。
構造的部分型は「シグネチャだけ揃ってれば置換可能でいいよね!」という割とゆるふわな感じ。
参考
構造的部分型 (structural subtyping) | TypeScript入門『サバイバルTypeScript』