ハードルを下げ続けるブログ@task

常に低い意識で投稿していきたいエンジニアのブログ。当ブログの内容は、個人の見解であり所属する組織の公式見解ではありませんよ。

NestJS で TDD (1) -- Controller

この記事は、ベーシックな REST API を TDD の手法を使って NestJS で実装していく過程をまとめたものです。全4パートで、各レイヤーのユニットテストと最後にE2Eテストについてご紹介します。このパートは Controller ユニットテストです。

記事作成時点では nestjs@7.6.15 です。

リポジトリ: GitHub - task-k0414/tdd-nestjs-exam-manager

はじめに

NestJS を業務で使いっていく中で、テストをどのように実装すればいいのか模索していたところ、

dzone.com

こちらの素晴らしい記事に出会いました。

この記事では、Controller, Service, Repository の 3レイヤーのアーキテクチャーを採用した、シンプルな REST API をTDDで実装していく過程が紹介されています。

これを元に、サンプルアプリを作ってみたので、同じくテストに思い悩む方の参考になれば幸いです。

背景

3つのレイヤーの説明

  • Controller - API を呼び出すエントリーポイント。レスポンスの成形や、リクエストのバリデーションなんかもこのレイヤー
  • Service - ビジネスロジックを扱うレイヤー。アプリケーションのコア
  • Repository - DB 操作を扱うレイヤー

NestJSではこれらのレイヤーを分離し、DIする方法を提供しています。

シナリオの設定

生徒(User)の試験結果を保存する必要があります。

  • 試験結果を入力する API はまだありません (実装する必要があります)
  • クラスの先生が、そのデータを入力しようとしています。

保存するデータ

{
  "userId": 1,
  "examId": 1,
  "score": 9
}

user と exam はそれぞれ存在確認が必要です。

score は 100点満点でそれを超えたデータはバリデーションで弾く必要があります

プロジェクトの準備

npx @nestjs/cli new tdd-nestjs-exam-manager
npm i

これで、app.module.ts 他、ルートになるモジュールが生成されます。

すでに、app.controller.spec.ts が用意されているので、テストを実行しておきます

npm test

テストがうまくいったら、試験結果を保存する、exam-result module を作成します NestJS CLI がジェネレーターを提供しているので、それを活用します

npx nest g module exam-result
npx nest g controller exam-result

TDD をしていく上で、どのレイヤーから実装していくべきか?という問題がありますが、 今回は深く考えずに、Controller から実装していきます。

Controller のテストを実装

まず、Controller の失敗するテストを書きます

// Controller
@Controller('exam-result')
export class ExamResultController {}
// test
  it('should call the save function', () => {
    const examResult = {}
    controller.save(examResult)
    expect(examResultService.save).toHaveBeenCalled()
  })

この時点で、IDEから警告がでてると思いますが、とりあえず無視します。

現時点で Controller の save method も examResultService もありません。

続いてエラーを解消するために、controller に save method を作り、service と連携させます。

npx nest g service exam-result
@Controller('exam-result')
export class ExamResultController {
  constructor(private readonly examResultService: ExamResultService) {}
  save(examResult: any) {
    this.examResultService.save(examResult)
  }
}

@Injectable()
export class ExamResultService {
  save(data: any) {
    throw new Error('Have not implemented')
  }
}

// test
describe('ExamResultController', () => {
  let controller: ExamResultController
  let examResultService: ExamResultService

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [ExamResultController],
      providers: [ExamResultService],
    }).compile()

    controller = module.get<ExamResultController>(ExamResultController)
    examResultService = module.get<ExamResultService>(ExamResultService)
  })

  it('should call the save function', () => {
    const examResult = {}
    controller.save(examResult)
    expect(examResultService.save).toHaveBeenCalled()
  })
})

ここで、Controller のユニットテストとしては、Service の実際の実装に関係がないので、Service をモックする必要があります

jest.mock('./exam-result.service')

これでテストが通りました。 続いて、Controllerのもう一つの仕事である入力データのチェックできる状態を作っていきます

入力データのバリデーション

まずは、リクエストデータの interface を定義します

export interface ISaveExamResultRequest {
  userId: number
  examId: number
  score: number
}

@Controller('exam-result')
export class ExamResultController {
  constructor(private readonly examResultService: ExamResultService) {}

  @Post()
  save(examResult: ISaveExamResultRequest) {
    this.examResultService.save(examResult)
  }
}

// test
  it('should call the save function', () => {
    const examResult: ISaveExamResultRequest = {
      userId: 1,
      examId: 1,
      score: 50,
    }
    controller.save(examResult)
    expect(examResultService.save).toHaveBeenCalled()
  })

入力されるデータが有効かどうかチェックします シナリオから制約は以下の通りです

  • user と exam はそれぞれ存在確認が必要です。
  • score は 100点満点でそれを超えたデータはバリデーションで弾く必要があります

まずは、score をバリデーションしてみます

@Controller('exam-result')
export class ExamResultController {
  constructor(private readonly examResultService: ExamResultService) {}

  @Post()
  save(examResult: ISaveExamResultRequest) {
    if (examResult.score < 0 || examResult.score > 100) {
      throw new BadRequestException('Validation failed')
    }
    this.examResultService.save(examResult)
  }
}

save() の中に直接バリデーションを実行しましたが、これでは単一責任原則に反しています。 また、今は score のみをバリデーションしていますが、入力されるデータのチェック項目はそれぞれのフィールドに対して必要で、同じフィールドに対しても複数のチェックが存在します。

そこで NestJS が提供する DTO と Validation Pipe の仕組みをつかってバリデーションを save() の外に出します。

DTO

今回は公式でも紹介されている class-validatorclass-transformer を使います

DTO とそのテストを用意します

// DTO
@Exclude()
export class SaveExamResultRequest implements ISaveExamResultRequest {
  @Expose()
  @IsNumber()
  @IsNotEmpty()
  userId: number

  @Expose()
  @IsNumber()
  @IsNotEmpty()
  examId: number

  @Expose()
  @IsNumber()
  @IsNotEmpty()
  @Max(100)
  @Min(0)
  score: number

  constructor(obj: Record<string, unknown> = {}) {
    Object.assign(this, obj)
  }

  static validate(obj: Record<string, unknown>) {
    return validate(new SaveExamResultRequest(obj))
  }
}

// test
describe('SaveExamResultRequest', () => {
  it('should return errors when any fields are empty', async () => {
    const errors = await SaveExamResultRequest.validate({})
    expect(errors).toHaveLength(3)
  })

  it('should return errors when any fields are string', async () => {
    const errors = await SaveExamResultRequest.validate({
      userId: 'user1',
      examId: 'exam1',
      score: 'scoreless',
    })
    expect(errors).toHaveLength(3)
  })

  it('should return an error when score is more than maximum value', async () => {
    const errors = await SaveExamResultRequest.validate({
      userId: 1,
      examId: 1,
      score: 120,
    })
    expect(errors).toHaveLength(1)
  })

  it('should return an error when score is less than minimum value', async () => {
    const errors = await SaveExamResultRequest.validate({
      userId: 1,
      examId: 1,
      score: -1,
    })
    expect(errors).toHaveLength(1)
  })
})

このDTOを body の型に指定して、global pipe で NestJS 標準の ValidationPipe を呼び出すよう設定します。 これで、Controller に処理が渡ってくる時点で正しいデータであることが保証されている状態がつくれたので、Controller自身は service.save() を呼び出す単一の役割に集中することができます。

// main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.useGlobalPipes(new ValidationPipe())
  await app.listen(3000)
}
bootstrap()

// Controller
save(@Body() examResult: SaveExamResultRequest) {
...
}

// test
  it('should call the save function', () => {
    const examResult: ISaveExamResultRequest = {
      userId: 1,
      examId: 1,
      score: 100,
    }
    controller.save(examResult)
    expect(examResultService.save).toHaveBeenCalledWith(examResult)
  })

Custom Pipe

続いて、User ID が存在しているかどうかのチェックを、Custom Pipe を用いて行います。

NestJSのCustom Pipeについては ドキュメント をご確認ください

generator を実行

npx nest g pi UserExistenceValidation --flat

UserService がすでにある体で進めていきます。

// Pipe
@Injectable()
export class UserExistenceValidationPipe implements PipeTransform {
  constructor(private readonly userService: UserService) {}

  async transform(value: { userId: number }) {
    if (!(await this.userService.exists(value.userId))) {
      throw new BadRequestException('User ID is not correct')
    }
    return value
  }
}

// test
jest.mock('./user.service')

describe('UserExistenceValidationPipe', () => {
  let userExistenceValidationPipe: UserExistenceValidationPipe
  let userService: UserService

  beforeEach(() => {
    userService = new UserService()
    userExistenceValidationPipe = new UserExistenceValidationPipe(userService)
  })
  it('should be defined', () => {
    expect(userExistenceValidationPipe).toBeDefined()
  })

  it('should throw validation error', () => {
    expect.assertions(1)
    jest.spyOn(userService, 'exists').mockResolvedValue(false)

    return expect(
      userExistenceValidationPipe.transform({ userId: 99 }),
    ).rejects.toThrowError()
  })

  it('should return validated object', () => {
    expect.assertions(1)
    jest.spyOn(userService, 'exists').mockResolvedValue(true)
    const payload = { userId: 99, anotherProperty: 'another property' }
    return expect(
      userExistenceValidationPipe.transform(payload),
    ).resolves.toEqual(payload)
  })
})

余談ですが、expect.assertions() を入れることで、実行されたテストの個数を確認することができます。非同期処理のテストでは、お作法を守らないとテスト実行がすり抜けてしまうため、必ず書くようにしましょう。

Exam ID にも同じものを用意します。(Userと全く同じなので省略)

NestJS の強力な DI で純粋な Controller テスト

これを@Body() の引数に渡すことで、関数の実行前に User の存在確認が行えます。 また、インスタンスではなくInjectable Class をそのまま渡すことで、必要な依存関係をModule側でDIできます。

@Controller('exam-result')
export class ExamResultController {
  constructor(private readonly examResultService: ExamResultService) {}

  @Post()
  save(
    @Body(UserExistenceValidationPipe, ExamExistenceValidationPipe)
    examResult: SaveExamResultRequest,
  ) {
    this.examResultService.save(examResult)
  }
}

// test
jest.mock('./exam-result.service')
jest.mock('../user')
jest.mock('../exam')

describe('ExamResultController', () => {
  let controller: ExamResultController
  let examResultService: ExamResultService

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [ExamResultController],
      providers: [ExamResultService],
    }).compile()

    controller = module.get<ExamResultController>(ExamResultController)
    examResultService = module.get<ExamResultService>(ExamResultService)
  })

  it('should call the save function', () => {
    const examResult: ISaveExamResultRequest = {
      userId: 1,
      examId: 1,
      score: 100,
    }
    controller.save(examResult)
    expect(examResultService.save).toHaveBeenCalledWith(examResult)
  })
})

ここまでで、クリーンな Controller とそのテストを実装することができました。

Controller って割といろんなサービスを呼び出したりして、複雑になってしまうことが多いな、と感じていたのですが NestJS の仕組みをつかってシンプルに片付けることができて感動しました。

次の記事では Service ユニットテストについてご紹介します。

参考

TDD Typescript NestJS API Layers with Jest Part 1: Controller Unit Test - DZone Web Dev

NestJS の DTO と Validation の基本 - 型定義とデコレータで安全にデータを受け付ける - Qiita

Nuxt + Composition-API のコンポーネント設計について考えたことなど

急にブログを書く気になったのでいっぱい更新してます。

前回の記事で Nuxt に Composition-API を導入する方法を書きました。

task-kawahara.hatenablog.com

今回は、半年間 Composition-API をつかってきて、どのように コンポーネント設計を行ってきたのかについての記事になります。

Motivation

  • Vuex store での Global state 管理をやめたい
  • UIとビジネスロジックを切り離して柔軟に設計したい
  • Clean Architecture を読んだから実践したい

まずは、Vue/Nuxt を使った比較的大規模な開発で辛いところをおさらいします。

Vuex 辛い問題

Vuex がよく辛いと言われますが、僕も辛いと思います。

巨大なグローバル変数になってしまうことが避けられず、どこからでもアクセスできてしまうのが割と悪いと感じてしまいます。

設計の問題が大きいと思いますが、何でもVuexしまっちゃいがちで、一つの名前空間のStoreに複数機能が入り乱れることがよく起こっていました。

よく考えると、ページをまたがって管理したい state はそんなに多くないんですよね。

型情報がうまく取れなくて辛いという問題もあるのですが、そこは Vuex4.0 でサポートされるみたいです。

ただし、現状SSR でページを跨ぐ状態管理は Vuex を使うのが無難。

Atomic Design よくわからん問題

Atomic Designコンポーネントを設計する上で素晴らしい概念ですが、よくよく考えないと見た目だけでコンポーネントを分割しがちです。

また、atoms, molecules, organisms の解釈が人によってブレがある気がします。

ルール化することが大事。

次は UI と ビジネスロジックを分離する方法を考えます。

Clean Architecture のエッセンス

  • 境界づけられたコンテキスト
  • Repository, UseCase, UI で分割
  • 依存関係逆転の原則

リソースにアクセスする Repositoryビジネスロジックを内包し、状態管理する StoreUI添えるだけ 見た目の表現に集中できるように分割したい。

f:id:task_kawahara:20201228160913p:plain
Repository, Store, UI

多分こんな感じ。

コンポーネント設計の方針

以上のことをまとめて、

  • Vuex はグローバルで扱いたいもの以外には使わず、Composition-API で Store パターンを実装する
  • Repository を定義する
  • Atomic Design のルールを定義する
    • Atoms これ以上分割できない最小単位
    • Molecules
      • オブジェクトとオブジェクトの持つ動作
      • データのやり取りは Props を介する
    • Organisms
      • 意味のある塊であり、使い回さないことも多い
      • provide/inject を使った変数や関数のやり取りを行う
    • Pages
      • Page をまたがないモジュールはここで provide する

ディレクトリ構成

root
┣ assets
┣ components
    ┣ (atoms)
    ┣ molecules
    ┗ organisms
┣ composables*
    ┣ repositories
    ┣ stores
    ┗ utils
        ┣ filters
        ...
┣ layouts
┣ middlewares
┣ plugins
┣ pages
...

ベーシックな Nuxt のファイル構成に、Compositon-API の 関数をまとめる /composablesディレクトリを追加しています。

composables の直下に、/storesrepositories を用意し、それぞれ Store と Repository を置きます。 その他、コンポーネントで使うツールみたいなもの(誤解を恐れずに言えば mixin の代わりになるもの)は /utils の下に置くことにしました。

Composables

以下は composables の例

~/composables/stores/ContentStore.ts

export interface ContentDataInterface {
  id: number
  detail: string
  title: string
}

export interface ContentStoreActionInterface {
  fetch: (id)=> Promise<ContentDataInterface>,
  create: (payload)=> Promise<ContentDataInterface>,
}
export const ContentRepositoryKey: InjectKey<ContentStoreActionInterface> = Symbol('ContentRepository')

export const useContentStore = () => {
  const repository: ContentStoreActionInterface | undefined = inject(ContentRepositoryKey)
  if (!repository) {
    throw new Error(`${ContentRepositoryKey} is not provided`)
  }
  ...
}

~/conposables/repositories/ContentRepositroy.ts

import { HttpKey } from './common

export const useContentRepository = (): ContentStoreActionInterface => ({
  const $http = inject(HttpKey)
  const fetch = (id) => $http.get('/contents/${id}')
  const create = (payload) => $http.post('/contents')
  return { fetch, create }
})

~/pages/index.vue

<script lang="ts">
import { defineCompoenent, provide } from '@nuxtjs/composition-api' 
import { useContentStore, ContentRepositoryKey } from '~/composables/stores/ContentStore'
import { useContentRepository } from '~/composables/repositories/ContentRepository'

export default defineCompoenent({
  setup () {
    provide(ContentRepositoryKey, useContentRepository())
    const store = useContentStore()
    ...
  }
})
</script>

Components

┣ components
    ┣ (atoms)
    ┣ molecules
        ┣ buttons
        ┣ forms
        ...
    ┗ organisms
        ┣ ContentEdit.vue
        ...

molecules はパーツごとに分類しています。organisms は一箇所でしか使わないパターンも多いので、必要になるまでは平置きでもいいかなという感じです。

個人的に、atoms と modules はどちらに分類するのか迷いがちなので、atoms は用意しませんでした。

molecules ↔ organisms は props でデータのやり取りをします。

organisms は InjectKey を定義して、pages から必要な変数や関数を provide() します。

このようにUIのレイヤーを明確にすることで、不毛なPropsリレーを避けるようにしています。

以下はコンポーネントの例です

~/components/molecules/forms/ValidationInput.vue

<template>
  <span class="input">
    <input v-model="data" />
    <small v-if="isInvalid">エラー</small>
  </span>
</template>

<script lang="ts">
import { defineCompoenent, PropType, computed } from '@nuxtjs/composition-api' 


export default defineCompoenent({
  props: {
    value: {
      type: String,
      required: true
    },
    validator: {
      type: Function as PropType<(str: string)=> Boolean>
      default: (str: string) => !!str
    }
  },
  setup (props, { emit }) {
    const data = computed({
      get: () => props.value,
      set: (v) => emit('input', v)
    })
    const isInvalid = computed(() => props.validator(props.value))
    return {
      data,
      isInvalid
    }
  }
})
</script>

~/component/organisms/ContentEditForm.vue

<template>
  <form>
    <ValidationInput v-model="content.title" />
    ...
  </form>
</template>

<script lang="ts">
import { defineCompoenent, InjectKey, inject } from '@nuxtjs/composition-api' 
import ValidationInput from '~/components/molecules/forms/ValidationInput.vue'

interface ContentInterface {
  title: string
  detail: string
} 

interface ContentEditInterface {
  content: ContentInterface
  fetch: ()=> Promise<void>
  create: ()=> Promise<void>
}

export const ContentEditKey: InjectKey<ContentEditInterface> = inject('ContentEdit')

export default defineCompoenent({
  components: {
    ValidationInput
  },
  setup() {
    const injected = inject(ContentRepositoryKey)
    if (!injected) {
      throw new Error(`${ContentRepositoryKey} is not provided`)
    }
    const { content, fetch, create } = injected

    ...

    return {
      content,
      ...
    }
  }
})
</script>

~/pages/index.vue

<script lang="ts">
import { defineCompoenent, provide } from '@nuxtjs/composition-api' 
import { useContentStore, ContentRepositoryKey } from '~/composables/stores/ContentStore'
import { useContentRepository } from '~/composables/repositories/ContentRepository'
import ContentEditForm, { ContentEditKey } from '~/components/organisms/ValidationInput.vue'

export default defineCompoenent({
  components: {
    ContentEditForm
  },
  setup () {
    provide(ContentRepositoryKey, useContentRepository())
    const store = useContentStore()
    provide(ContentEditKey, store)
    ...
  }
})
</script>

あとがき

Vue 3.0 (Composition-API) のリリースによって、Vue/Nuxt を使ったフロントエンド開発の幅が広がりました。

それ故に、コンポーネント間のデータの受け渡し一つ取っても複数の書き方が存在し、迷うことがより多くなった印象です。設計する側に求められるレベルが一段上がった感じがしています。

この記事がベストというわけではありませんが、Composition-API を使っていきたいけどハードルが高いと思っている方の助けに少しでもなれば幸いです。

書いてて思ったのですが、Clean Architecture に関してはすごいなんちゃって感ありますね。。。

気持ち真似したくらいに思って貰えると幸いです。。。

Nuxt で Vue 3.0 (Composition-API) を使いたいとき

ja.nuxtjs.org

composition-api.nuxtjs.org

Vue3.0 がリリースされてからしばらく立ちましたが、Nuxt において Vue3.0 がサポートされた v3.0 がリリースされるのはもうちょっとかかりそうです。

とはいえ、v3.0 がリリースされるのを待って、無理やりバージョンをあげようとしてもおそらく、かなりの痛みを伴うことが、他の皆様のVue3.0移行記事からも読み取れます。

そこで、Vue 3.0 リリースより少し前から、既存の Nuxt Project に composition-API を導入した所感と、導入方法を記録しておきます。

というか、この過渡期においてNuxtへの Composition-API 導入についての日本語記事があまり見当たらなかったので、少しでも参考になれば幸いです。

なお、バージョンは記事公開時点の最新とします。

Nuxt.js v2.14.12
@nuxtjs/composition-api v0.17.0

Motivation

  • Vue 3.0 (Nuxt v3) にスムーズに移行できるように準備する
  • Composition-API を使いたい
  • TypeScript をより使いやすくしたい

Composition-API の導入

@nuxtjs/composition-api を使います。 少し古い記事だと、@vue/composition-api を導入している記事が多いですが、@vue/composition-apiAPIは @nuxthjs/~ で全て公開されているので、今ならNuxt.js 用のものを使った方が良さそうです。また、後述しますがSSRで本格的に Composition-API を使いたい場合は、@nuxtjs/composition-api を使わないと辛いと思います。(とはいえまだRFCなので、ご利用は慎重に。。。)

各 mode 共通

$ npm install @nuxtjs/composition-api --save
{
  buildModules: [
    '@nuxtjs/composition-api'
  ]
}

※ TypeScript の導入は省略

SPA の場合

Vue Composition-API の書き方で大丈夫です。Plugin や Context を使いたいときは root をつかった方が今はいいと思います。 Nuxt 独自の非同期API である asyncDatafetch() についてはなるべく使わず、Vueの書き方に徹した方が、移行しやすいです。

import { defineComponent } from '@nuxtjs/composition-api'

export default defineComponent ({
  setup (_, { root }) {
    const store = root.$store
    const myPlugin = root.$myPlugin
    // 変数に代入しておけば、あとあと root を書き換えやすい
  }
})

SSR の場合 (SSGも同様)

前述のSPAの場合ではなるべくNuxt の独自hook は使わないようにできましたが、SSRが必要な場合はそうもいきません。 @vue/composition-api では、下記のようにキメラにするしかなかったですが、この方法だとテンプレート上の変数情報が失われてしまい、DXが悪くなります。

また、vue-meta を使う場合、setup で定義されたものにアクセスできないので、動的な変更が難しくなります。

キメラの例

import { ref } from '@nuxtjs/composition-api'

export default Vue.extend ({
  head () {
    return {
      title: 'タイトル' // setup 内でアクセスできないため、書き換えできない
    }
  },
  async asyncData ({ app }) {
    const posts = await app.$http.$get('/api/posts')
    return {
      posts // setup でアクセスできない
    }
  },
  setup (_, { root }) {
    const active = ref(false)
    return {
      active
    }
  }
})

@nuxtjs/composition-api では、従来の asyncData, fetch() そして head()に変わる API が用意されています

useAsync - Nuxt Composition API

useFetch - Nuxt Composition API

useMeta - Nuxt Composition API

これらを使うことで、page component でも Composition-API を存分に使うことができますね!

ただ、時期尚早感もあるので、SSR の Pages ではComposition-APIを使わないというのも手だと思います。

半年ほど Nuxt + Composition-API を使った所感

VueとTypeScript は相性が悪いと言われていましたが、Composition-API を導入することで劇的に改善していきました。

とくに、既存のプロジェクトのUIデザイン刷新のタイミングで、Composition-API を使い、state の管理を Vuex store からどんどん移行していったところ、コードも非常にスッキリし、単一責任の原則をより意識して書く事ができるようになり、とても満足しています。

ただ、現段階での辛いところとしては、まだベストプラクティスと呼べるものが出てない(もしくは認知されてない)ので、手探りでやっていく必要があることと、自由度が高いので、プログラマーJavaScript力やリテラシーが試されることがあります。

そういった面を踏まえて、RFCだからといって実用に耐えないかというとそんなことはなく、Nuxt v3.0のリリースも控えてますし、導入できるところはどんどん活用していって知見をためていけるといいのではないでしょうか。

別の記事で、Composition-API をつかったコンポーネント設計についても書きたいと思います。

追記: 書きました

task-kawahara.hatenablog.com

今年1年で買ったキャンプギア

この記事はmohikanz Advent Calendar 2020 5日目の記事です

adventar.org

昨日の担当はまだいないので、これを見た誰かが埋めてくれると嬉しいなー

3日目は nagashi_ma_w さん でした。


mohikanz は、一応技術者を中心とした雑談Slackですが、まぁ今日は休みの日ですし、当ブログもテックブログというわけでもないので、趣味の話します。

知らない人のために軽く自己紹介を

  • 元転職エージェント
  • エンジニアにキャリアチェンジして3.5年くらい
  • この1年はほぼTypeScript書いて過ごしました
  • もともとキャンプに憧れててせっせと道具を買い集めて、去年ぐらいから本格的にキャンプ開始

で、タイトル通り今年買ったキャンプギアを振り返って行きたいわけなんですが、

あまりに数が多いので(ギア買うために副業まではじめました)、

今年の Best Buy を抜粋してご紹介したいと思います。

フュアーハンド社 ハリケーンランタン ベビースペシャル 276 ジンク

芸人のヒロシさんも愛用していることで知られるハリケーンランタンです。

もう、こいつを点けにキャンプに行っていると言っても過言ではないくらい、今年行った全てのキャンプに持っていってます。

僕は割と軽量ソロキャンプ志向なので、結構持っていくには勇気の要る大きさなのですが、こいつがあるかないかだけで、キャンプの気分が本当に違う。

ランタンの光と、焚き火の光を眺めて飲むお酒は本当に美味しいです。

間違いなく今年一番買ってよかったキャンプギアです。

優しく温かみのあるランタンの光

昨今の狂ったキャンプブームにより価格が超高騰しているので注意。

僕が春に買ったときは4000円代だったので、、、

Naturehike FP800 高級ダウンマミー型シュラフ ULG400

みんなの味方、中華ギアです。

カタログスペックで、快適温度 -4℃, 限界温度 -8℃, エクストリーム-14℃ となっていますがユーロノームなどの国際規格ではなく独自規格なので、多少割り引いて考える必要はあります。

とはいえ、ほぼ同等のスペックで4シーズン向けのモンベル ダウンハガー #3 が 33,000円もするのに、半額以下の 15,000円 程度で買えちゃいますし、フィルパワー800 なのでめちゃくちゃコンパクトにできるすごいやつです。

12月〜1月あたりなら、夏用の化繊シュラフと二重にすれば結構いけちゃうので、冬用シュラフとして今シーズン愛用しています。

Keith チタニウムウッドストーブ

Keith というこれまた、中国のチタン製ギア専門のブランドのポケットサイズウッドストーブ

全部分解してきれいに重ねられるので、とてもコンパクトでソロにもってこいでした。

f:id:task_kawahara:20201205002416p:plain

BUNDOK(バンドック) ソロ ティピー 1 BDK-75

【Amazon.co.jp 限定】BUNDOK(バンドック) ソロティピー1 BDK-75AM

【Amazon.co.jp 限定】BUNDOK(バンドック) ソロティピー1 BDK-75AM

  • 発売日: 2019/07/01
  • メディア: スポーツ用品

去年まではテントを持たずに、タープにハンモック泊で過ごしていたのですが、テントほしい場面もあるなーと思い購入しました。本当は同社の軍幕テントのソロベースがほしかったけど高くて買えなかった。

注意すべき点は、タープ代わりになる跳ね上げが無いことと、難燃素材ではないため、焚き火の火の粉で穴が開く可能性があることです。

ですが、その分軽量で、バックパックでの持ち運びも容易なので、僕のキャンプスタイルにはすごく合っていました。

買ってよかったです。

ヘリノックス コンフォートチェア

みんなの憧れヘリノックスチェア(本物)を買ったよって自慢です。

もっぱら家で使ってます。家で使っても違和感なく、使い勝手もいいので最高です。でも高い。

ただ、最初に買って使っていた、パチノックス(中華製のコピー品) は見るも無残に壊れてしまったので、

なんだかんだ、値段には理由があるのかなぁと感じました。高いけど。


全然紹介しきれてないですが、今年買った中で、ヘビーユーズしてるものを紹介しました。

今年は、コロナ自粛開けからキャンプ場の場所を確保するのも大変でしたが、

結構充実したキャンプライフを全うできたかなと、満足しています。

今日もこれから、上で紹介した道具を持ってゆるキャン聖地 四尾連湖 水明荘にキャンプしに行ってきます!

明日は、shiranuik さんです

【日本語訳】Announcing TypeScript 4.0 (2) [WIP]

Announcing TypeScript 4.0 日本語訳のその2です。

やっと本編です。

devblogs.microsoft.com

何が新しいのか?

これらすべてが、4.0 を導いたのです!それでは、さっそく何が新しいのか詳しく見ていきましょう。

Variadic Tuple Types

配列かタプルの2つの引数を取り、それらを連結して新しい配列をつくる JavaScript の関数 concat を検討してみましょう。

function concat(arr1, arr2) {
    return [...arr1, ...arr2];
}

また、配列かタプルを取り、一番目以外の要素すべてを返す tail 関数も考えてみましょう。

function tail(arg) {
    const [_, ...result] = arg;
    return result
}

これらのどちらか TypeScript で型付けするとどのようになるでしょうか?

concat の場合、過去の言語バージョンでできることで唯一有効なのは、いくつかのオーバーロードを書いて試してみることです。

function concat(arr1: [], arr2: []): [];
function concat<A>(arr1: [A], arr2: []): [A];
function concat<A, B>(arr1: [A, B], arr2: []): [A, B];
function concat<A, B, C>(arr1: [A, B, C], arr2: []): [A, B, C];
function concat<A, B, C, D>(arr1: [A, B, C, D], arr2: []): [A, B, C, D];
function concat<A, B, C, D, E>(arr1: [A, B, C, D, E], arr2: []): [A, B, C, D, E];
function concat<A, B, C, D, E, F>(arr1: [A, B, C, D, E, F], arr2: []): [A, B, C, D, E, F];)

あーっと、これは、、、2つ目の配列が空だったときが、7個のオーバーロードだったということです。 arr2 が一つの要素を持つ場合を足してみましょう。

function concat<A2>(arr1: [], arr2: [A2]): [A2];
function concat<A1, A2>(arr1: [A1], arr2: [A2]): [A1, A2];
function concat<A1, B1, A2>(arr1: [A1, B1], arr2: [A2]): [A1, B1, A2];
function concat<A1, B1, C1, A2>(arr1: [A1, B1, C1], arr2: [A2]): [A1, B1, C1, A2];
function concat<A1, B1, C1, D1, A2>(arr1: [A1, B1, C1, D1], arr2: [A2]): [A1, B1, C1, D1, A2];
function concat<A1, B1, C1, D1, E1, A2>(arr1: [A1, B1, C1, D1, E1], arr2: [A2]): [A1, B1, C1, D1, E1, A2];
function concat<A1, B1, C1, D1, E1, F1, A2>(arr1: [A1, B1, C1, D1, E1, F1], arr2: [A2]): [A1, B1, C1, D1, E1, F1, A2];

きりが無いのがお分かりいただけると思います。 不幸にも、tail のような関数でも同じタイプの問題に出くわすでしょう。

これは私達が、「1000のオーバーロードによる死」とよんでいる別のケースで、一般的に問題を解決することすらできません。それは、ただ書く必要のあるオーバーロードの数だけ正しい方を用意するだけです。もしキャッチオールケースを作りたいなら、以下のようなオーバーロードが必要になります。

function concat<T, U>(arr1: T[], arr2: U[]): Array<T | U>;

ですが、このシグネチャは入力の長さや、タプルを使用する際の要素の順序については何もエンコードしていません。

TypeScript 4.0 は、型推論の改善に伴って2つの重要な変更がなされてこれらの型付けが可能になりました。

1つ目の変更は、タプル型構文のスプレッドがジェネリックになったことです。これは、扱う実際の型を知らない時でも、タプルと配列での高次演算を表現できるようになったことを意味します。ジェネリックスプレッドが、これらのタプル型でインスタンス化された時(もしくは、実際の型で置き換えられた時)、それらは配列やタプルの型の他のセットを生成できます。

例えば、「1000のオーバーロードによる死」に陥らず、tail のような関数に型付けできるとこを意味しています。

function tail<T extends any[]>(arr: readonly [any, ...T]) {
    const [_ignored, ...rest] = arr;
    return rest;
}

const myTuple = [1, 2, 3, 4] as const;
const myArray = ["hello", "world"];

// type [2, 3, 4]
const r1 = tail(myTuple);

// type [2, 3, 4, ...string[]]
const r2 = tail([...myTuple, ...myArray] as const);

【日本語訳】Announcing TypeScript 4.0 (1)

はじめに

TypeScript 4.0 がリリースされました 🎉🎉

Variadic Tuple Types などタプル周りの型が大きく強化されています。

公式の紹介記事を一通り、読んだのでより理解すべく日本語訳してブログに乗せておきたいと思います。 わかりやすさのため一部意訳しています。また、導入は省略します。

誤訳や間違った部分があれば教えて下さい。

devblogs.microsoft.com

なお、TypeScript 4.0 では大きな互換性を破壊する変更は無いとのことです。

With TypeScript 4.0, there are no major breaking changes.

The 4.0 Journey

TypeScript は今日、JavaScript 技術スタックの中核となっています。npm 上では、7月には初めて月間ダウンロード数が5000万を突破しました!また、私達は常に成長と改善の余地があると認識していますが、TypeScript を使っている人のほとんどが、コーディングを楽しんでくれているのは明らかです。 StackOverflow の最新の開発者アンケートではTypeScriptが最も好きな言語の第二位にランクインしていますし、最新の State of JS Survey では、TypeScript 使った開発者の89%がまた使うだろうと言っています。

私達のここまでの道のりを少し掘り下げてみましょう。過去2つのメジャーバージョンでは、長きに渡り輝いてきたハイライトを振り返ってきました。TypeScript 4.0 でもその伝統を引き継ぎます。

3.0 以降を振り返って、めまいがするほど多くのの変更がありましたが、TypeScript 3.0 自体が強力なものになりました。 タプル型と引数リストを統合したことは大きなハイライトであり、既存の JavaScript パターンの多くを関数で利用できるようになりました。また、このリリースでは、コードベース全体でのスケールアップ、整理、共有を支援するためのプロジェクトリファレンスも紹介しています。インパクトの大きい一つの小さな変更点は、3.0 が unknown という any の型安全な代替型を用意したことでした。

TypeScript 3.1 では、タプル型や配列型で動作するように Mapped types の機能が拡張され、使用されなくなった TypeScript 固有のランタイム機能に頼ることなく関数にプロパティを割り当てることが劇的に容易になりました。

TypeScript 3.2 はジェネリック型のオブジェクトスプレッドが可能になり、3.0の能力を駆使し、 bindcallapply の厳密な型付けをすることで、関数を使ったメタプログラミングをより良くモデル化することができました。

TypeScript 3.3 では、3.2に続いて安定性にちょっとだけ焦点を当てましたが、同時に Union 型メソッドを使用する時の QOL の改善をもたらし、--build モード下での file-incremental build を追加しました。

TypeScript 3.4にて、イミュータブルなデータ構造のサポートをより充実させて関数型パターンをより深くサポートすることに傾向し、高次ジェネリック関数の型推論を改善しました。大きな追加点として、このリリースでは、--incremental フラグが導入されました。このフラグを使用すれば、TypeScriptを実行するたびにフルリビルドが走るのを回避して、より早くコンパイルと型チェックの結果を得ることができます。

TypeScript 3.5 と 3.6 では、よりスマートな互換性検証ルールとともに、型システムルールが厳格化されました。

TypeScirpt 3.7 はECMAScriptと新しい型システムの豊富な組み合わせを特徴としており、非常に注目に値するリリースでした。型システム側から見ると、再帰的な型エイリアス参照とアサーションスタイル関数のサポートがありました。JavaScript側では、オプショナルチェーンと合体が追加されました。これは、TypeScirptとJavaScript のユーザー双方にとって最も要望の多かった機能であり、TC39で私達のチームが一部で支持したものでした。

もっと最近の 3.8 と 3.9 では、型のみの imports/exports や private fields のような ECMAScript の機能、module 内でのトップレベawait 、新しい export * シンタックス が導入されました。

言語サービス、インフラストラクチャ、Webサイト、その他のコアプロジェクトなど、TypeScript の体験にとって非常に価値のある作業のすべてにはまだ触れていません。コアチームのプロジェクト以外にも、エコシステムに信じられないほど貢献してくれるコミュニティがあり、(TypeScript の) 体験を推進し、DefinitelyTypedやTypeScriptそのものを支援してくれています。DefinitelyTyped が始まった2012年には、当初 80 PRほどしかありませんでしたがその年末には増加に転じています、2019年には、8300を超える PR があり、これは今でも私達を驚かせています。このようなたくさんの貢献は、TypeScript の体験にとって礎となるものであり、私達がエコシステムを改善し、常に改良していくための後押しをくれる、活気に満ち溢れた熱心なコミュニティに感謝しています。

(その2に続く)


全訳って大変ね… TypeScirpt 3.x の振り返り部分だけしか訳せてないけど、モチベ維持したいので公開しておきます。 3.x の振り返りにはいい感じでした

誰のためのデザイン? #1

誰のためのデザイン? 改訂版 D.A.ノーマン 新様社 の読書メモ

今回は第1章の導入部分。

  • 工学的な論理を前提にデザインすることが原因で起こる、 人間−機械のインタラクションの欠陥
  • その上で 人間中心デザイン(HCD)という哲学に基づいてデザインすべきという主張

一旦区切って、次の記事で アフォーダンス についてから書く。

第1章 毎日使う道具の精神病理学

人間−機械のインタラクションの欠陥が生じる理由

(大半のデザインが)テクノロジーの専門家であるが人間への理解が少ない技術者たちによってなされるから

技術者はものごとを論理的に考えるように訓練されている。 その結果、すべての人々が論理的に考えると錯覚してしまう。 だから、論理的説明さえあれば人々はそれを使いこなすことができると過信する。

何年か前に電通出身のインフルエンサー(というのか?)のはぁちゅう氏が

電通の先輩が、『CMは偏差値40の人にも理解できるものじゃなきゃダメ。この会社にいる時点で普通ではないと自覚しろ。世間にはおそるべき量のおそるべきバカがいる。そしてそれが日本の「普通の人」だ』って言ってたの、一番役に立ってる教えの一つだ」

なんてツイートしたものだから、大炎上した*1というエピソードがある。

非常に言葉が強く炎上するのも無理はないのだけど、本人にとって本当に役立っているのだろう。*2

要は、人間は 相手も同じ様に考えるだろう と考えてしまうものだから、気をつけようね(超意訳) という訳である。

世の中のほとんどはデザインを担当する人間とは違う人生を歩んできているのだから、

人間の振る舞いを、こうあるべきと思うようにではなく、あるがままに受け入れなければならない。

著者は、スリーマイル島原発事故の調査に参加し、根本原因はオペレーターのヒューマンエラーではなく、デザインの欠陥によるものだと突き止めた仕事の経験から、このような見解に至ったと述べている。完全に余談だけど、大学でやった工学倫理の講義で同原発事故のケーススタディでそんなこと言ってなかったとおもうので、大学ではもっとデザインの重要性にも言及すべきだと思う。

人間中心デザイン Human-Centered Design

コミュニケーションは、ものごとがうまく行ってないときには、とりわけ重要である。ものごとがうまく行っているときだけスムーズに協調して動くものをデザインするのは比較的容易である。

強制的にWFHさせられてる今、これはわかりみが深い。

HCDの原則は、できるだけ長い間、問題を特定することを避け、その代わり、暫定的なデザインを繰り返していくことにある。

アジャイルっぽい

HCDはデザインの哲学と進め方であるため、インダストリアルデザイン、インタラクションデザイン(UIデザイン)、エクスペリエンスデザイン(UXデザイン)のどれとも共立する。

*1:彼女の場合、いつでも燃え盛っているので、この程度で大炎上とは言えないかもしれない

*2:実際に本当に役立っているのだとしたら、このまま書いたら炎上しそうだということくらい想像つくと思うのだが。まぁ彼女の場合、炎上を狙ったものである可能性が高い。