Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

コードには型アノテーションよりも要件アノテーションを増やせ!/harajukuts2

OKUNOKENTARO
January 28, 2022

 コードには型アノテーションよりも要件アノテーションを増やせ!/harajukuts2

2022/1/28 Harajuku.ts Meetup #2 にて使用した資料です。

11ページ目はString#join()ではなくArray#join()の誤りでした。お詫びいたします。

OKUNOKENTARO

January 28, 2022
Tweet

More Decks by OKUNOKENTARO

Other Decks in Technology

Transcript

  1. ۃ୺ͳྫ function add(a, b) { return a + b; }

    ࣮૷ऀ͸਺஋ͷ଍͠ࢉΛ૝ఆ࣮ͯ͠૷ͨ͠ͷʹɺ 
 ར༻ऀ͸͜ͷؔ਺Λจࣈྻͷ࿈݁ͷͨΊͩͱޡղͯ͠͠·ͬͨ ʂ
  2. ҙࢥૄ௨ͷϛε w ίʔ υΛॻ͍͍ͯΔ্Ͱͷϛε͸UZQPɺ ͏ ͔ͬΓ࡟আͳͲ༷ʑ w ։ൃऀಉ࢜ͷҙࢥૄ௨͕ίʔ υ্Ͱ׬݁͠ͳ͍͜ͱ΋ 


    ҙࢥૄ௨ͷϛεͱݺ΂Α ͏ w จࣈྻ࿈݁ؔ਺ͱޡղͯ͠͠·ͬͨ։ൃऀ͸ɺ 
 add()ؔ਺͕Ͳ͏͍͏ؔ਺ͳͷ͔࣮૷ऀຊਓʹฉ͘ ·Ͱ೺ѲͰ͖ͳ͔ͬͨ
  3. ଞਓͷίʔ υɺ ਖ਼͠ ͘ղऍͯ͠ಡΜͰ·͔͢ ʁ w ΋ͪΖΜ͜Ε͸͘͢͝ۃ୺ͳྫ 
 ʢ+ ͸਺஋ͷՃࢉʹͷΈ༻͍ͯɺ

    จࣈྻ࿈݁͸ String#join() ΍5FNQMBUFMJUFSBMTΛ࢖͏ํ͕Α Γ਌੾ʣ  w ͔͠͠ɺ ۀ຿͸͜͏͍ͬͨ ʮଞਓͷίʔ υΛᴥᴪͳ͘ղऍͰ͖͍ͯΔ͔ʯ ͱ͍͏ 
 ධՁͷ࿈ଓͰ͋Δ w ίʔ υதͷ৘ใྔ͕๡͍͠ͱɺ ͨͱ͑1BSTBCMFͳίʔ υͰ͋ͬͨͱͯ͠΋ 
 ଞͷ։ൃऀͷҙਤ·Ͱղऍ͢Δͷ͸೉͍͠
  4. ܕΞϊςʔγϣϯ function add(a: number, b: number): number { return a

    + b; } 5ZQF4DSJQUͰ͸ɺ Ҿ਺΍໭Γ஋ͷܕΛදه͢Δ 
 ͜ΕΛܕΞϊςʔγϣϯ 5ZQFBOOPUBUJPOT ͱ͍͏
  5. ܕΞϊςʔγϣϯ function add(a: number, b: number): number { return a

    + b; } ܕΞϊςʔγϣϯͷ͓͔͛Ͱɺ 5ZQF4DSJQUίϯύΠ ϥ͚ͩͰͳ͘ 
 ίʔ υΛಡΉਓؒʹ΋ҙਤΛղऍ͢Δखॿ͚ͱͳΔ ʮ͋ɺ ͜Ε͸number͔ͩΒจࣈྻ࿈݁ʹ࢖͏ؔ਺͡Όͳ͍ͳ ʂ ʯ
  6. ཁ݅Ξϊςʔγϣϯ ʁ w ຊߘͷ଄ޠ w ͪΐͬͱΩϟονʔͳλΠ τϧʹ͔͚ͨͬͨͩ͠ͳͷͰ 
 άάͬͯ΋ެࣜͷ υΩϡϝϯ

    τͱ͔͸ग़͖ͯ·ͤΜ w ܕΞϊςʔγϣϯʹܕҎ্ͷ৘ใྔΛ΋ͨͤΔ͜ͱͰ 
 ೝࣝͷᴥᴪΛΑ Γແ͘ ͢͜ͱΛ໨తͱ͢Δͷ͕ຊ࣭
  7. ͜ ͏͍͏ ͜ͱɺ ͨ͜͠ͱ͋Δਓ type UserId = string; type BookId

    = string; ຊΛ؅ཧ͢ΔΞϓϦ 
 UserId΋BookId΋จࣈྻͳΜ͚ͩͲ۠ผ͍ͨ͠ʜ
  8. type UserId = string; type BookId = string; async function

    fetchBook(id: BookId): Promise<Book> { // } export function BookCard(): JSX.Element { const [book, setBook] = useState<Book | null>(null); useEffect(() => { (async (): Promise<void> => { const result = await fetchBook(); setBook(result); })(); }, []); return <></>; } ͜Μͳঢ়گ͕͋ͬͨͱ͠·͠ΐ ͏
  9. type UserId = string; type BookId = string; async function

    fetchBook(id: BookId): Promise<Book> { // } export function BookCard(): JSX.Element { const [book, setBook] = useState<Book | null>(null); useEffect(() => { (async (): Promise<void> => { const result = await fetchBook(); setBook(result); })(); }, []); return <></>; } fetchBook()ؔ਺ʹ͸BookIdܕͷҾ਺Λ౉ͤ͹Αͦ͞͏ Ͱ͢
  10. ίϯύΠ ϥ͸۠ผ͠·ͤΜ w typeΛ࢖ͬͯstringʹผ໊Λ͚ͭͯ΋ 
 ͦΕ͸͋͘ ·Ͱ΋ΤΠ ϦΞε ʢผ໊ʣ ʹ͗͢ͳ͍

    w ͳͷͰυΩϡϝϯ τʹ΋ͪΌΜͱ5ZQF"MJBTFTͱॻ͔Ε͍ͯΔ 
 https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-aliases  w ਓؒʹ͸ผ໊Ͱ͋Δ͜ͱ͸ࣝผͰ͖Δ͕ 
 ίϯύΠ ϥʹ͸stringʹผ໊Λ෇͚ͨ͜ͱ͔͠఻Θ͍ͬͯͳ͍ w UserIdͱBookId͕ޓ͍ʹstringͷผ໊Ͱ͋Δͱ͍͏͚ͩͰ 
 ͦΕΒ͕ޓ͍ʹ۠ผ͞Ε͏ Δ΂͖ͱ͍͏ཁ݅·Ͱ͸ίϯύΠ ϥʹ఻Θ͍ͬͯͳ͍
  11. ͳΒ͹Ϋϥε͔ ʁ class UserId { constructor(public readonly value: string) {}

    } class BookId { constructor(public readonly value: string) {} } UserIdͱBookIdΛͦΕͧΕclassͰએݴͯ͠Έͨ
  12. 5ZQF4DSJQU͸4USVDUVSBM5ZQJOHͰ͋Δ w 5ZQF4DSJQU͸ܕͷผ໊Ͱ۠ผͨ͠Γɺ Ϋϥε໊͕ҟͳΔ͜ͱͰ۠ผ͢ΔͷͰ͸ͳ͘ 
 ৗʹ ʮϓϩύςΟͷߏ੒ʯ ʹΑͬͯܕͷ۠ผΛ͚ͭΔ w ͜͏͍ͬͨܕͷ൑ผํࣜΛ4USVDUVSBM5ZQJOHͱ͍͏

    w ͜Ε͸υΩϡϝϯ τʹ΋هࡌ͞Ε͍ͯΔ 
 https://www.typescriptlang.org/docs/handbook/type-compatibility.html  w ܕ໊ͷҧ͍͚ͩͰܕ͕ҟͳ͍ͬͯΔͱ൑ผ͢Δํࣜ͸/PNJOBM5ZQJOHͱ͍͏ w 5ZQF4DSJQU͸ͪ͜Βͷํࣜ͸࠾༻͍ͯ͠ͳ͍
  13. ϓϩύςΟͷҰக class UserId { constructor(public readonly value: string) {} }

    class BookId { constructor(public readonly value: string) {} } valueͱ͍͏ ϓϩύςΟ͕ݸ͋ΔΫϥε 
 ͱ͍͏఺Ͱɺ ߏ଄͕Ұக͍ͯ͠Δ 
 Αͬͯ4USVDUVSBM5ZQJOHʹج͍ͮͯ͜ΕΒ͸ಉҰܕͱΈͳ͞ΕΔ
  14. ϓϩύςΟ ΛҰகͤ͞ͳ͍Α ͏ʹ͢Δ class UserId { userId: unknown; constructor(public readonly

    value: string) {} } class BookId { bookId: unknown; constructor(public readonly value: string) {} } ͦΕͧΕͷΫϥεʹҟͳΔ໊લͷμϛʔϓϩύςΟ Λ଍͢͜ͱͰ 
 ߏ଄͕Ұக͠ͳ͍Α ͏ʹͨ͠
  15. const id = new UserId('ckyx6c8xc000009jm8acefv2v'); const result = await fetchBook(id);

    Argument of type 'UserId' is not assignable to parameter of type 'BookId'. Property 'bookId' is missing in type 'UserId' but required in type 'BookId'.(2345) Τϥʔʹͳͬͯ ͘Εͨ
  16. Ϧςϥϧͱ ͯ͠ѻ͑ͳ͍ w new UserId()͍ͯ͠ΔͨΊɺ ݁Ռ͸typeof id === 'object'ͱͳΔ w

    จࣈྻϦςϥϧͰ͸ͳ͘ɺ valueΛϓϩύςΟ ͱ ͯ࣋ͭ͠ΦϒδΣΫ τʹͳΔ w toString()Λ࣮૷͢Ε͹[object Object]͸ճආͰ͖Δ͕ 
 ͜ͷख๏Λࢼ͢ࡍʹຖճͦͷ࣮૷͕ඞཁ w μϛʔϓϩύςΟ Λੜ΍͞ͳ͖ΌࣝผͰ͖ͳ͍ͷ΋ඇຊ࣭త w Ϧςϥϧͷ··ѻ͑ͯɺ ͔ͭ/PNJOBM5ZQJOHͬΆ͍ܕ൑ผΛ͢Δʹ͸ʜ ʁ
  17. UZQF/PNJOBM w type Nominal<T, U>Λએݴ͢Δ w { __brand: U }ͱ͍͏ΦϒδΣΫ

    τͱͷަࠩܕ & ʹ͢Δ͜ͱͰϦςϥϧͷ·· ۠ผͰ͖ΔΑ ͏ʹ͢Δ w ਖ਼֬ͳग़య͸ൃݟͰ͖͍ͯͳ͍͕ෳ਺ͷจݙ͋Γ w #SBOEFE5ZQFTͱ΋ݺ͹Ε͍ͯΔ͕ 
 ʮ/PNJOBM5ZQJOHΛ࣮ݱ͍ͨ͠ͷ͔ͩΒʯ ͱ͍͏҆௚ͳ೔ຊਓతޠ๏Ͱ 
 ࠓճ͸Nominal<T, U>ͱͨ͠ export type Nominal<T, U extends string> = T & { __brand: U };
  18. /PNJOBMܕΛ࢖ͬͯ࠶௅ઓ type Nominal<T, U extends string> = T & {

    __brand: U }; type BookId = Nominal<string, 'BookId'>; type UserId = Nominal<string, 'UserId'>; const id = 'ckyx6c8xc000009jm8acefv2v' as UserId; const result = await fetchBook(id); Argument of type 'UserId' is not assignable to parameter of type 'BookId'. Type 'UserId' is not assignable to type '{ __brand: "BookId"; }'. Types of property '__brand' are incompatible. Type '"UserId"' is not assignable to type '"BookId"'.(2345) ͪΌΜͱ۠ผͰ͖͍ͯΔ
  19. type Nominal<T, U extends string> = T & { __brand:

    U }; type BookId = Nominal<string, 'BookId'>; type UserId = Nominal<string, 'UserId'>; const id = 'ckyx6c8xc000009jm8acefv2v' as UserId; const result = await fetchBook(id); ʮͰ΋BTېࢭͯ͠·ͤΜͰͨ͠ ʁ ʯ
  20. "TTFSUJPOGVODUJPOTΛ࢖͏ Α ͏ʹ͢Δ w ͜ͷख๏Ͱ͸ΧδϡΞϧʹas͕ग़͖ͯͯ͠·͍ ʮBTېࢭʯ ʹ൓ͯ͠͠· ͏ w ͦͷͨΊɺ

    Nominal<T, U>ܕͷ஋Ͱ͋Δͱ͍ࣔͨ͠৔߹͸ඞͣ 
 5ZQFQSFEJDBUFTɺ ·ͨ͸"TTFSUJPOGVODUJPOTΛ࢖͏ Α ͏ʹ͢Δ
  21. BTTFSU4USJOH ؔ਺Λ࡞ͬͯΈΔ export function isString(v: unknown): v is string {

    return typeof v === 'string'; } export function assertString(v: unknown, target = ''): asserts v is string { if (!isString(v)) { throw new Error(`${target} should be string`.trim()); } } ౰ͨΓલͷΑ ͏ͳ಺༰ͷؔ਺͕ͩɺ 
 unknownܕΛstringܕͱΈͳͨ͢Ίʹ͸ɺ Θ͟Θ͟͜͏͍ͬͨؔ਺͕ඞཁ
  22. /PNJOBMܕͱ"TTFSUJPOGVODUJPOTͷԠ༻ import { isString } from './assert-string'; import { Nominal

    } from './nominal'; type FilledString = Nominal<string, 'FilledString'>; function isFilledString(v: unknown): v is string { return isString(v) && v !== ''; } function assertFilledString( v: unknown, target = '' ): asserts v is FilledString { if (!isFilledString(v)) { throw new Error(`${target} should be not empty string`.trim()); } } type FilledStringΛએݴ͠ɺ ඞۭͣจࣈྻͰ͸ͳ͍͜ͱΛද໌͢Δ
  23. /PNJOBMܕͷෳ߹ import { FilledString, assertFilledString } from '../utils/filled-string'; import {

    Nominal } from '../utils/nominal'; export type UserId = Nominal<FilledString, 'UserId'>; function assertUserId(v: unknown): asserts v is UserId { assertFilledString(v); } export function asUserId(v: unknown): UserId { assertUserId(v); return v; } ͞Βʹɺ FilledStringܕΛຬͨ͢൑ผՄೳܕͱͯ͠type UserIdΛએݴ͢Δɻ 
 ಉख๏Ͱએݴ͢Δtype BookIdͱίϯύΠ ϥ͕۠ผͰ͖Δ্ʹ 
 ۭจࣈؚ͕·Εಘͳ͍͜ͱ΋อূࡁΈ ʂ 
 ʢ"TTFSUJPOGVODUJPOʹ͸ࣗಈςε τͷ࣮ࢪ͕ඞਢʣ
  24. ܕΞϊςʔγϣϯ͸ ʮܕ͔͠Θ͔Βͳ͍ʯ w fetchBook(id: string)Ͱ͸ 
 ͳΜΒ͔ͷจࣈྻΛҾ਺ʹͱΔ͜ͱ ͔͠Θ͔Βͳ͍ w ͦΕ͸UserId͔΋͠Εͳ͍͠ɺ

    ۭจࣈྻΛ౉ͯ͠΋͍͍͔΋͠Εͳ͍ w ܕΞϊςʔγϣϯ͔Β͸ ʮstringܕͷ஋Ͱ͋Δ͜ͱʯ Ҏ֎ͳʹ΋Θ͔Βͳ͍ w ೥લͷ+BWB4DSJQUˠ5ZQF4DSJQUͷ࣌୅͸ɺ ͜ΕͰ΋ཱ೿ͳ΋Μͩͬͨ
  25. ܕΞϊςʔγϣϯʹؚΊΔ৘ใྔΛ૿΍͢ w type BookId = Nominal<FilledString, 'BookId'>Λએݴ͓ͯ͘͠ ͱ 
 fetchBook(id:

    BookId)ͱ͍͏γάωνϟ͔ΒಘΒΕΔ৘ใྔ͕ 
 fetchBook(id: string)ʹൺ΂͙ͯͬͱ૿͢ w ͦΕ͸UserIdͰ͸μϝͱΘ͔Δ w ͦΕ͸ۭจࣈྻͰ͸μϝͱΘ͔Δ w ࣮૷୲౰ऀʹޱ಄Ͱฉ͍ͯճΔඞཁ͕ͳ͍ w ͜ͷߟ͑ํ͸ܖ໿ϓϩάϥϛϯά ʢ%FTJHOCZ$POUSBDU %C$ͳͲͱ΋ʣ ͷ 
 ํ๏࿦ʹج͍͍ͮͯΔ w ಛʹࣄલ৚݅΍ෆม৚݅ʹ֘౰
  26. த਎ͷ͋ΔΞϊςʔγϣϯ΁ w ཁ݅ΛܕΞϊςʔγϣϯʹؚΊΔʕʕཁ݅Ξϊςʔγϣϯʹ͢Δ͜ͱͰ 
 ߦͷίʔ υ͔ΒಡΈखʹ఻ΘΔҙਤ͕Α Γ๛͔ʹͳΔ w ͜Ε·Ͱͷ೥͸+BWB4DSJQU͔Β5ZQF4DSJQU΁ͷա౉ظͩͬͨ w

    ͜Ε͔Β͸ίʔ υ͕5ZQF4DSJQUͱͯ͠ɺ 5ZQF4DSJQUΒ͘͠੒ख़͍ͯ࣌͘͠୅ w 5ZQF4DSJQUͱ ͯ͠Α Γ๛͔ͳදݱΛ͢ΔͳΒ 
 த਎ͷͳ͍ΞϊςʔγϣϯΑ Γத਎ͷ͋ΔΞϊςʔγϣϯΛ৺͕͚ΔͱΑ͍