Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

2bedb1eb8f841cd3c3ae584600b016e0?s=47 OKUNOKENTARO
January 28, 2022

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

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

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

2bedb1eb8f841cd3c3ae584600b016e0?s=128

OKUNOKENTARO

January 28, 2022
Tweet

More Decks by OKUNOKENTARO

Other Decks in Technology

Transcript

  1. +BO )BSBKVLVUT.FFUVQ !PLVOPLFOUBSP ίʔ υʹ͸ܕΞϊςʔγϣϯΑ Γ ΋ 
 ཁ݅ΞϊςʔγϣϯΛ૿΍ͤ ʂ

  2. ୭ w Ԟ໺ݡଠ࿠!PLVOPLFOUBSP w ΫϨε΢ΣΞגࣜձࣾ w 5ZQF4DSJQUྺ೥ w ϑϩϯ τΤϯ

    υ ɾ όοΫΤϯ υ
  3. 5ZQF4DSJQUొஃͷྺ࢙

  4. 5ZQF4DSJQUొஃͷྺ࢙ ࠓ೔͸͜Εͷଓ͖Ͱ͢ˢ

  5. ͳͥ5ZQF4DSJQUΛ࢖͏ͷ͔ ʁ w +BWB4DSJQU͸ॻ͍͍ͯͯϛεΛ͢Δݴޠ͔ͩΒͰ͋Δ w ΋ͪΖΜ5ZQF4DSJQU΋ॻ͍͍ͯͯϛεΛ͢ΔݴޠͰ͋Δ w ϛε͔ͯ͠Βϛεʹؾ෇͘ ·Ͱͷ͕࣌ؒ୹͚Ε͹୹͍΄Ͳخ͍͠ w

    ͦ΋ͦ΋ϛε͠ͳ͍Α ͏ͳߏจͰ͋Ε͹΋ͬͱخ͍͠ wʮઈରʹϛεΛ͠ͳ͍ਓʯ ͕։ൃ͢ΔͳΒ+BWB4DSJQUͷ··͓޷͖ʹͲ͏ ͧ
  6. ਓ͸ϛεΛ͢ΔΜͰ͢ ʂ

  7. ۃ୺ͳྫ ͜Ε͸+BWB4DSJQUͷίʔ υ 
 ͭͷ਺஋Λ଍ͯ͠߹ܭΛฦ͢add()ؔ਺ function add(a, b) { return

    a + b; }
  8. ۃ୺ͳྫ Ͱ΋ add("Hello", "World") ͳΜͯ͜ͱ΋Ͱ͖Δ 
 ʢ݁Ռ͸)FMMP8PSMEʣ function add(a, b)

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

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


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

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

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

    + b; } ܕΞϊςʔγϣϯͷ͓͔͛Ͱɺ 5ZQF4DSJQUίϯύΠ ϥ͚ͩͰͳ͘ 
 ίʔ υΛಡΉਓؒʹ΋ҙਤΛղऍ͢Δखॿ͚ͱͳΔ ʮ͋ɺ ͜Ε͸number͔ͩΒจࣈྻ࿈݁ʹ࢖͏ؔ਺͡Όͳ͍ͳ ʂ ʯ
  14. ʜͱ͍͏࿩͸೥લޙʹࢄʑ͞Ε· ͨ͠

  15. ೥͸ܕΞϊςʔγϣϯॻ͍ͯ·͢ΑͶ w ͕͢͞ʹ5ZQF4DSJQU΋όʔδϣϯYʹಥೖ͠੒ख़͖ͯͨ͠ w पΓͰॻ͍͍ͯΔ։ൃऀ΋͔ͳΓ૿͑ͨ w --noImplicitAny --noImplicitReturns ͳͲͷ 


    UTDPO fi H$PNQJMFS0QUJPOTͷ͓͔͛Ͱ 
 Ξϊςʔγϣϯͷॻ͖࿙ΕΛ๷͙ํ๏΋௕೥ཱ֬͞Ε͍ͯΔ
  16. ܕΞϊςʔγϣϯ͸Έͳ͞Μॻ͍͍ͯΔͱࢥ͍·͢ͷͰɺ 
 ͜Ε͔Β͸ ʮཁ݅Ξϊςʔγϣϯʯ Λॻ͍ͯΈ·ͤΜ͔

  17. ཁ݅Ξϊςʔγϣϯ ʁ w ຊߘͷ଄ޠ w ͪΐͬͱΩϟονʔͳλΠ τϧʹ͔͚ͨͬͨͩ͠ͳͷͰ 
 άάͬͯ΋ެࣜͷ υΩϡϝϯ

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

    = string; ຊΛ؅ཧ͢ΔΞϓϦ 
 UserId΋BookId΋จࣈྻͳΜ͚ͩͲ۠ผ͍ͨ͠ʜ
  19. 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 <></>; } ͜Μͳঢ়گ͕͋ͬͨͱ͠·͠ΐ ͏
  20. 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ܕͷҾ਺Λ౉ͤ͹Αͦ͞͏ Ͱ͢
  21. const id: BookId = 'ckyx69gli000009ju65mvhyw7'; const result = await fetchBook(id);

    Αͦ͞͏
  22. const id: UserId = 'ckyx6c8xc000009jm8acefv2v'; const result = await fetchBook(id);

    ຊ౰ʹ ʁ ʁ ʁ 
 ίϯύΠϧ௨ͬͯΔΑ ʁ ʁ ʁ
  23. const result = await fetchBook('͜Μʹͪ͸'); ͳΜͳΒ͜Ε΋௨Δ

  24. ίϯύΠ ϥ͸۠ผ͠·ͤΜ w typeΛ࢖ͬͯstringʹผ໊Λ͚ͭͯ΋ 
 ͦΕ͸͋͘ ·Ͱ΋ΤΠ ϦΞε ʢผ໊ʣ ʹ͗͢ͳ͍

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

    } class BookId { constructor(public readonly value: string) {} } UserIdͱBookIdΛͦΕͧΕclassͰએݴͯ͠Έͨ
  26. const id = new BookId('ckyx69gli000009ju65mvhyw7'); const result = await fetchBook(id);

    ͕͢͞ʹࠓ౓͸Αͦ͞͏ ʁ
  27. const id = new UserId('ckyx6c8xc000009jm8acefv2v'); const result = await fetchBook(id);

    ͩΊͰ͢ 
 ͜Ε΋ίϯύΠϧ͕௨Δ
  28. 5ZQF4DSJQU͸4USVDUVSBM5ZQJOHͰ͋Δ w 5ZQF4DSJQU͸ܕͷผ໊Ͱ۠ผͨ͠Γɺ Ϋϥε໊͕ҟͳΔ͜ͱͰ۠ผ͢ΔͷͰ͸ͳ͘ 
 ৗʹ ʮϓϩύςΟͷߏ੒ʯ ʹΑͬͯܕͷ۠ผΛ͚ͭΔ w ͜͏͍ͬͨܕͷ൑ผํࣜΛ4USVDUVSBM5ZQJOHͱ͍͏

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

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

    value: string) {} } class BookId { bookId: unknown; constructor(public readonly value: string) {} } ͦΕͧΕͷΫϥεʹҟͳΔ໊લͷμϛʔϓϩύςΟ Λ଍͢͜ͱͰ 
 ߏ଄͕Ұக͠ͳ͍Α ͏ʹͨ͠
  31. 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) Τϥʔʹͳͬͯ ͘Εͨ
  32. ʜͱ͍͏࿩͸೥ʹ͠· ͨ͠

  33. ʜͱ͍͏࿩͸೥ʹ͠· ͨ͠ 5ZQF4DSJQUಋೖͰಘΒΕΔ ʮม͍͑ͯ͘༐ؾʯ 
 https://speakerdeck.com/okunokentaro/the-courage-to-change-by-typescript

  34. Ϋϥε๏͸Կ͕Πέͯͳ͍͔ w 5ZQF4DSJQUͰΫϥεશൠΛ࢖͏ ͜ͱͷੋඇΛड़΂ΔΘ͚Ͱ͸ͳ͍ w /PNJOBM5ZQJOH ʮͬΆ͍ʯ ͜ͱΛ͢ΔͨΊʹΫϥεΛಋೖ͢Δͷ͸Πέͯͳ͍ w ͳ͔ͥ

    ʁ
  35. const id = new UserId('ckyx6c8xc000009jm8acefv2v'); await fetch(`https://something/api/users/${id}`); 5FNQMBUFMJUFSBMTʹͯ ͜ͷΑ ͏ʹύεʹ*%ؚ͕·ΕΔΑ

    ͏ͳ63-͸Α ͘ ͋Δ 
 ݁Ռ͸ ʁ
  36. https://something/api/users/[object Object] 👆 👆 👆 👆 👇 👇👇 👇 👇

  37. Ϧςϥϧͱ ͯ͠ѻ͑ͳ͍ w new UserId()͍ͯ͠ΔͨΊɺ ݁Ռ͸typeof id === 'object'ͱͳΔ w

    จࣈྻϦςϥϧͰ͸ͳ͘ɺ valueΛϓϩύςΟ ͱ ͯ࣋ͭ͠ΦϒδΣΫ τʹͳΔ w toString()Λ࣮૷͢Ε͹[object Object]͸ճආͰ͖Δ͕ 
 ͜ͷख๏Λࢼ͢ࡍʹຖճͦͷ࣮૷͕ඞཁ w μϛʔϓϩύςΟ Λੜ΍͞ͳ͖ΌࣝผͰ͖ͳ͍ͷ΋ඇຊ࣭త w Ϧςϥϧͷ··ѻ͑ͯɺ ͔ͭ/PNJOBM5ZQJOHͬΆ͍ܕ൑ผΛ͢Δʹ͸ʜ ʁ
  38. ྩ࿨࠷৽൛

  39. 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 };
  40. /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) ͪΌΜͱ۠ผͰ͖͍ͯΔ
  41. 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ېࢭͯ͠·ͤΜͰͨ͠ ʁ ʯ
  42. લճͷ͓͞Β͍ https://speakerdeck.com/okunokentaro/techstand6 Ԟ໺ݡଠ࿠͸લճͷ5ZQF4DSJQUొஃʹͯBTΛېࢭ͍ͯͨ͠ ʂ ʂ ʂ

  43. "TTFSUJPOGVODUJPOTΛ࢖͏ Α ͏ʹ͢Δ w ͜ͷख๏Ͱ͸ΧδϡΞϧʹas͕ग़͖ͯͯ͠·͍ ʮBTېࢭʯ ʹ൓ͯ͠͠· ͏ w ͦͷͨΊɺ

    Nominal<T, U>ܕͷ஋Ͱ͋Δͱ͍ࣔͨ͠৔߹͸ඞͣ 
 5ZQFQSFEJDBUFTɺ ·ͨ͸"TTFSUJPOGVODUJPOTΛ࢖͏ Α ͏ʹ͢Δ
  44. 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ܕͱΈͳͨ͢Ίʹ͸ɺ Θ͟Θ͟͜͏͍ͬͨؔ਺͕ඞཁ
  45. /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Λએݴ͠ɺ ඞۭͣจࣈྻͰ͸ͳ͍͜ͱΛද໌͢Δ
  46. /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ʹ͸ࣗಈςε τͷ࣮ࢪ͕ඞਢʣ
  47. Ԡ༻ͷՄೳੑ͸ແݶେ w ͜ͷख๏Λ׆༻͢Δͱ Ϧςϥϧͷදݱͷ෯͕޿͕Δ w ྫ͑͹ʜ w ۭจࣈྻΛېࢭ͢Δ6TFS*Eܕ w ಛఆͷਖ਼نදݱΛຬ͍ͨͯ͠ͳ͍ͱΤϥʔͱͳΔ'PSNFEܕ

    w খ਺஋΍/B/ΛೝΊͳ͍6OJY5JNFܕ w ۠ผՄೳͳ4FDPOEܕͱ.JMMJTFDPOEܕ w ͳͲͳͲ
  48. ܕΞϊςʔγϣϯ͸ ʮܕ͔͠Θ͔Βͳ͍ʯ w fetchBook(id: string)Ͱ͸ 
 ͳΜΒ͔ͷจࣈྻΛҾ਺ʹͱΔ͜ͱ ͔͠Θ͔Βͳ͍ w ͦΕ͸UserId͔΋͠Εͳ͍͠ɺ

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

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

    ͜Ε͔Β͸ίʔ υ͕5ZQF4DSJQUͱͯ͠ɺ 5ZQF4DSJQUΒ͘͠੒ख़͍ͯ࣌͘͠୅ w 5ZQF4DSJQUͱ ͯ͠Α Γ๛͔ͳදݱΛ͢ΔͳΒ 
 த਎ͷͳ͍ΞϊςʔγϣϯΑ Γத਎ͷ͋ΔΞϊςʔγϣϯΛ৺͕͚ΔͱΑ͍
  51. 5IBOLZPV https://commons.wikimedia.org/wiki/File:IRS46_nasa.jpg