OmniAICreator/Qiita-1.07M huggingface dataset:
「世界で闘うプログラミング力を鍛える本」Pythonコード解答例 - 1.3 URLify


「世界で闘うプログラミング力を鍛える本」Pythonコード解答例 - 1.3 URLify

#目次
###CHAP1. 配列と文字列
1. [重複のない文字列](https://qiita.com/YusukeEngineer/items/494ccb49de66b39d16c0)
2. [同じ文字の数を数える](https://qiita.com/YusukeEngineer/items/4ed19c5f6efa23c62351)
3. [URLify](https://qiita.com/YusukeEngineer/items/4208561f8e1ac04cbbf4)
4. [回文の順列](https://qiita.com/YusukeEngineer/items/5d05620f3d9e9cc4e7b5)
5. [一発変換](https://qiita.com/YusukeEngineer/items/122360b8a95f6eb858f7)
6. [文字列圧縮](https://qiita.com/YusukeEngineer/items/0bd139a361ce3f4eac36)
7. [行列の回転](https://qiita.com/YusukeEngineer/items/4b2fd1ed64385612cba6)
8. ["0"の行列](https://qiita.com/YusukeEngineer/items/bed1ce30d4ef966fe142)
9. [文字列の回転](https://qiita.com/YusukeEngineer/items/2b6f9d2a733d2815a1ac)

#Pythonコード解答例
```python
def replaceSpaces(str,trueLength):

    spaceCount = 0

    for i in range(trueLength):
        if str[i] == " ":
            spaceCount = spaceCount + 1

    index = trueLength + spaceCount * 2

    target = [0] * index

    for i in range(trueLength-1,-1,-1):

        if str[i] == " ":
            target[index-1] = "0"
            target[index-2] = "2"
            target[index-3] = "%"
            index = index - 3

        else:
            target[index-1] = str[i]
            index = index - 1

    return "".join(target)

input_str = "Mr John Smith"
input_num = len(input_str)

print(replaceSpaces(input_str,input_num))
```
BigQueryにGCSのcsvファイルを日付別シャード化テーブルとして一括インポートする方法

GCSに格納した日付別の(※)csvファイルをシャード化テーブルとしてBigQueryに一括でインポートする方法のメモ。
_※同一カラムをもつが、日付別にファイルが分割されている_
パーティショニングとシャーディングの違いについては[Google公式ドキュメント](https://cloud.google.com/bigquery/docs/partitioned-tables?hl=ja)をご参照ください。

# 前提
- Google Cloud Platformでプロジェクトを作成済みであること
- 作成済みプロジェクトに対して課金が有効になっていること
- [Google Cloud SDK](https://cloud.google.com/sdk/install?hl=ja)をインストール済みであること

# 準備
## GCSへBigQueryにインポートしたいcsvを格納

以下の構成でGCSにBigQueryにインポートしたいcsvを格納します。
ファイル名の"\_"以降がシャード化される日付になります。

```GCS格納レイアウト
bucket
└── dir
    ├── test_20200101.csv
    ├── test_20200102.csv
    ├── test_20200103.csv
    …
    └── test_20200131.csv
```

## スクリプト作成
GCSからBigQueryにインポートするためのスクリプトを作成します。
ローカルに以下のファイルレイアウトを用意します。

```:ローカルのファイルレイアウト
dir
├── .env
└── import_bq_from_gcs.sh
```

### 設定ファイル

```:.env
# プロジェクトID
PROJECT_ID=********
# BQにインポートしたいcsvを格納しているパス
GCS_PATH=gs://bucket/test_bq_1
# インポート先データセット名
DATA_SET=test_bq
# インポート先テーブル名(prefix)
TABLE_PREFIX=table
```
### インポートスクリプト
GCSにあるインポート対象のcsvごとにBigQueryにインポートしていきます。

```sh:import_bq_from_gcs.sh
#!/bin/sh

set -e

# 環境変数
. .env
# プロジェクトIDを設定
gcloud config set project ${PROJECT_ID}
echo "set PROJECT_ID"
# 指定したGCSパス配下のファイルごとに処理
for file_full_path in `gsutil ls ${GCS_PATH}`
do
  # ファイルパス抽出
  file_path=${file_full_path##*/}
  # ファイル名抽出
  file_name=${file_path%.*}
  # ファイル名suffix('_'以降)抽出
  file_suffix=${file_name##*_}
  table_name=${DATA_SET}.${TABLE_PREFIX}_${file_suffix}
  # ファイル名が存在
  if [ ${#file_name} -gt 0 ]; then
    bq load --autodetect --source_format=CSV ${table_name} ${file_full_path}
    echo "import from ${file_full_path} to ${table_name}"
  fi
done

```

# GCSからBigQueryにインポート
準備完了後、`.env`、`import_bq_from_gcs.sh`と同一ディレクトリにて以下を実行します。

```
sh sh:import_bq_from_gcs.sh
```

実行後、BigQueryのコンソールを確認すると、日付別シャード化テーブルとしてインポートできていることを確認できました！

<img width="1397" alt="bq_import.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337794/802ff263-d23a-84d5-88d0-d7ac04b76645.png">
【Rails】I18nの定義存在チェック

# 指定したkeyがI18n定義済みか確認したい

`I18n.exists?(#{key})` で確認できます。

```yml:config/locales/ja.yml
ja:
  users:
    index:
      title: ユーザ一覧
```

```ruby:
I18n.exists?('users.index.title')
=> true # 定義済みなので true

I18n.exists?('users.show.title')
=> false # そんな定義は無いので false
```
【Vue.js】子コンポーネント内でリストレンダリングしているボタンに親コンポーネントで定義したイベントを動的に対応させるサンプルコード

## はじめに
**「子コンポーネント内でリストレンダリングしているボタン」**
↕
**「親コンポーネントで定義したイベント」**

を**動的に対応**させたかったのでサンプルコードを残します。

※コピペで挙動を確認できます。

## 環境
```yaml
OS: macOS Catalina 10.15.3
Vue: 2.6.10
```

## 結論：出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483292/a04919c8-fd1d-71f6-e45f-1a203fd6e6ca.png)


このそれぞれのボタンに別々のメソッドが紐付いています。

## 子コンポーネント
```vue:Child.vue
<template>
  <div id="child">
    <h1>{{ title }}</h1>
    <!-- 親コンポーネントのbtns内のオブジェクト数に応じてbuttonの数を変更する -->
    <div v-for="btn in btns" :key="btn.id">
      <!-- $emitにて、親コンポーネントでv-onに指定する名前、第２引数には親コンポーネントに渡したい値をセット -->
      <button @click="$emit('click-button', btn.action)">{{ btn.name }}</button>
    </div>
  </div>
</template>

<script>
  export default {
    name: 'Child',
    props: {
      title: String, //型を指定しておくと予定外の型になった場合エラーが出て気付ける
      btns: Array,
    }
  }
</script>
```

## 親コンポーネント

```vue:Parent.vue
<template>
  <div id="parent">
    <!-- 子コンポーネントからpropsで渡した属性を親コンポーネントで指定 -->
    <Child
      title="Chile to Parent"
      :btns='btns'
      @click-button="toggleClick"
    ></Child>
  </div>
</template>

<script>
  import Child from './Child.vue'

  export default {
    name: 'Parent',
    components: {
      Child,
    },
    data() {
      return {
        //表示したい数だけbtnsにオブジェクトを記載
        btns: [
          {id: 1, name: 'run console.log', action: 'consoleLog'},
          {id: 2, name: 'run alert', action: 'alert'},
          {id: 3, name: 'run console.error', action: 'error'}
        ]
      }
    },
    methods: {
      //どのメソッドを発火するかを動的に変更するために定義
      toggleClick(action) {
        if (action === "consoleLog") {
          this.consoleLog() //thisをつけることで親コンポーネントで定義済のメソッドと紐づく
        } else if (action === "alert") {
          this.alert()
        } else if (action === "error") {
          this.error()
        }
      },

      //実際に発火するメソッド
      consoleLog() {
        console.log('one!')
      },
      alert() {
        alert('two!')
      },
      error() {
        console.error('three!')
      }
    }
  }
</script>
```

## おわりに
最後まで読んで頂きありがとうございました:bow_tone1:

どなたかの参考になれば幸いです:relaxed:

## 参考にさせて頂いたサイト（いつもありがとうございます）
- [Vue 親コンポーネントのデータ/メソッドを子コンポーネントから使う - Qiita](https://qiita.com/Sa2Knight/items/7e22307436a1e696729d)
- [【Vue.js】Vue warn: Error in v-on handler: &quot;TypeError: handler.apply is not a function - Qiita](https://qiita.com/terufumi1122/items/5254f69956be600b6af4)
PostgreSQLで、整数をbit配列にしよう 0→{}, 5→{1,0,1}

#1. やりたいこと
整数を入力したら、それに対応するbit(1)[]型のデータを返却するSQL文を作ろう。
つまり、
13を入力して、{1,1,0,1}、
1を入力して、{1}、
0を入力して、{}、
を返してくれるSQL文を作ってみよう。

#2. 答え
```sql
SELECT ('{'||rtrim(replace(replace(ltrim(ここに入力::bit(49)::text,'0'),'1','1,'),'0','0,'),',')||'}')::bit(1)[];
```

#3.解説

```sql
SELECT
('{'||
    rtrim(
            replace(
                replace(
                    ltrim(入力::bit(63)::text,'0')
                    --63bit 2進数に変換し、
                    --それをtextにキャストし文字列化、
                    --上位bitの0はltrimで粛清。
                    -- bigint最大値は2^63-1だ。
                ,'1','1,') --　例:1101→1,1,01,
            ,'0','0,') -- 例:1,1,01,→1,1,0,1,
    ,',') --例:1,1,0,1,→1,1,0,1
||'}')::bit(1)[];--キャストは外からやればよい。

```

※bigintで扱えない範囲の整数については対応していない。
migrateコマンドのrollbackのstepの挙動を調べた。

# 目的

```
php artisan migrate:rollback --step=5
```
このステップ５は、５回目に叩いたマイグレートファイルのみを削除するのか、それとも１～５回目のマイレートコマンドで作成したテーブル全て削除するのかどっちか知りたい、

# 条件
➀postsテーブルを先に作成して、マイグレートコマンドを実行
➁likesテーブルを先に作成してマイグレートコマンド実行
➂

```
php artisan migrate:rollback

Rolling back: 2020_01_31_211540_create_likes_table
Rolled back:  2020_01_31_211540_create_likes_table (0.02 seconds)
```
stepなしだと直前にやったlikesテーブルが削除される。

⓸もう一度、マイグレートコマンドでlikesテーブルを再度作成

⓹step=2のロールバックコマンドを実行、ここで１、２回のlikes、postsテーブル両方が削除されるのか、それとも２回目前だけのpostsテーブルのみが削除されるのかどっちなのか

```
$ php artisan migrate:rollback --step=2
Rolling back: 2020_01_31_211540_create_likes_table
Rolled back:  2020_01_31_211540_create_likes_table (0.02 seconds)
Rolling back: 2020_01_31_210238_create_posts_table
Rolled back:  2020_01_31_210238_create_posts_table (0.01 seconds)
```
答えは前者の両方が削除される、みたいです。
スライドショー作成の考え方

# スライドショーを構成する要素

- メインに表示される画像
- ボタン群(liで表現)
    - 再生ボタン
    - 前へボタン
    - 次へボタン
- サムネイルの画像一覧(liで表現)

# Step1 必要なパーツをHTMLとCSSで表現

- メインに表示される画像を任意で一枚とりあえず配置
- ボタン群をliタグで表現
    - ボタンに`user-select: none;`すると連打してもテキストが選択されないようになる
- サムネイルの画像一覧をliタグで表現
    - 画像の縦横比を考慮する必要があるが、widthのみ設定し、heightを省略すれば縦横比を保ってくれる
    - 選択されている画像と選択されていない画像が判別できるようにCSSを適用する(.current)

# Step2 上記のパーツをjavascriptで表現

- 画像ファイル名を配列で保持
    - メインエリアに配列のインデックスで画像を指定して表示
    - サムネイル一覧エリアに配列ループで表示
- currentクラス
    - currentクラスを付与する時には配列のインデックスを指定する時に$array[currentIndex]とする

# Step3 javascriptで動的な表現を実装

## サムネイルをクリックしたらメインに反映される

- サムネイルの画像全てにクリックイベントを設置し、メインエリアにそのクリックされた画像パスを格納する
- currentクラスの付け替え
    - 一旦currentクラスを削除する
    - currentクラスを付与するための**現在選択されている配列のインデックス番号を更新する**
    - 上記のノードにcurrentクラスを付与する

## 次へボタン

- クリックイベントを設定し、**現在選択されている配列のインデックス番号の1つ上の値**に上記と同様の処理を走らせる。
- サムネイルの最後まで表示させたら最初に戻るようにif文を記述

## 前へボタン

- 次へボタンの処理を流用

## 再生ボタン

- 次へボタンの処理をsetTimeout()で一定リズムで走らせる
    - [HTMLElement​.click()](https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/click)により、**あたかも手動でクリックしたかのように動作させることができる**
- 停止ボタンを動作させる(フラグ変数isPlayingを用意,初期値はfalse)
    - falseの場合
        - 再生の処理を走らせ、テキストをplayからpauseに変更
        - フラグを切り替える(`isPlaying = !isPlaying;`)
    - trueの場合
        - clearTimeout()にタイマーIDをセットし、処理を止める
        - 再生の処理を走らせ、テキストをpauseからplayに変更
        - フラグを切り替える(`isPlaying = !isPlaying;`)
unknown な値が interface を満たしているか調べる関数を書く


~~[ajv](https://www.npmjs.com/package/ajv) を使え。~~

ajv を導入するほどでもないかなというときは object かどうかを確認する関数だけ定義しておくと便利です。

```ts
const isObject = (d: unknown): d is Record<keyof any, unknown> =>
  'object' === typeof d && !Array.isArray(d) && !!d

interface Account {
  readonly id: string
  name: string
}

const isAccount = (x: unknown): x is Account =>
  isObject(x) && 'string' === typeof x.id && 'string' === typeof x.name
```

## 使用例

```ts
for (const txt of [
  '{"id":"qwer","name":"taro"}',
  'null',
  '{}',
]) {
  const a: unknown = JSON.parse(txt) 
  if (isAccount(a)) {
    console.log('ok', a.id, a.name)
  } else {
    console.log('err')
  }
}
// ok qwer taro
// err
// err
pygameでタイマー機能を実装する

# はじめに
pygameというライブラリを用いて、
指定した時間が経過した後音楽を流すという、いわゆるタイマーのようなプログラムを作成としたとき、
エラーメッセージも表示されず、音楽も流れないという不具合が出た。
その解決法について書いていく。

# 環境
Python 3.6.8
pygame 1.9.6

# 結論
`pygame.mixer.music.play()` によって音楽が出力されるとき、
`time.sleep()`で音楽を再生する時間だけプログラムの実行に遅延を発生させることで音楽が流れるようになった。

# 作成したコード

### 問題が起きたコード

```python
# 音楽を再生するプログラムを作成する

import pygame
import time

react_time = int(input("何秒後に音楽を鳴らすかセットしてください >>"))

print(react_time)
time.sleep(react_time)

pygame.mixer.init()
pygame.mixer.music.load("BGM.mp3")
pygame.mixer.music.play()

print("music play")
```
というコードを実行し、ターミナルで仮に3と入力するとき、

```
何秒後に音楽を鳴らすかセットしてください >>3
3
music play
```
という結果だけが出力され、音楽は流れない。

### 問題解決したコード

`pygame.mixer.music.play()`の次に、`time.sleep()`によって遅延を発生させることで、
音楽が流れるようになった。

```python
import pygame
import time

react_time = int(input("何秒後に音楽を鳴らすかセットしてください >>"))

print(react_time)
time.sleep(react_time)

pygame.mixer.init()
pygame.mixer.music.load("BGM.mp3")
pygame.mixer.music.play()

time.sleep(10)  # time.sleep()で処理終了までの時間を確保する

print("music play")
```

# 参考
pythonのpygameで音が鳴らない
https://teratail.com/questions/173534
Macbookに入れているアプリ一覧備忘録

## はじめに
はじめまして、ひろたつです。
現在はエンジニアはやっていませんが、過去にITの会社でエンジニアをしていたこともあり、作業効率化アプリに関しては異常にこだわりがあります。

そこで今回は、僕が選びに選んだ便利アプリを皆さんに共有したいと思います。
（実際は、僕自信の備忘録として残しておきたいです）

ちなみにですが、全て無料です。有料アプリは基本的に入れません。

なにかの参考になれば幸いです。

ではどうぞ

## 環境
macOS Catalina 10.15.2

## 便利アプリ群
### ⌘英かな
USキーボードを使ってるので、こいつは必須
Karabinerとは友だちになれなかった。。

### Alfread 3
言わずとしれた、なんでも屋さん
うまく使えば無限の可能性を秘めている
spotlightとは友だちになれなかった。。

### Android File Transfer
Android端末を使ってる人は必須アプリ
GoProのファイルも見れるから便利
今どきiPhoneじゃななくてAndroidだよね、？うん？

### AppCleaner
アプリのアンインストーラー
関係のあるファイルをまとめて消してくれる
完璧ではないだろうけど、気休め程度に
普通に削除するよりはマシだろう

### Bandwidth+
ネットの通信速度を常時可視化してくれる
めっちゃ使いやすい
最高、神、仏様

### Clipy
言わずとしれた、作業効率化アプリ
コピペ職人には必須だろう

### Docker
アプリ開発時に役に立つ
現役時代は必須アプリだったが最近は出番が少ない（てかないｗ）

### FileZilla
ファイル共有アプリ
あんまし使ってないような

→アンインストール済み

### Fotor Photo Editor
画像編集ソフト
そこまで使いやすいわけではないし、他にもっとよいアプリがあると思うんだけど、なんか使ってる
友だちになっちゃったからねぇ、しょうがないねぇ

### Funter
隠しファイル（「.」で始まるファイル）を表示してくれる
エンジニアやってたときは必須アプリだった
今は、、まあすべてのアプリが表示されるのは気分的に楽かな

### Google Chrome
ブラウザアプリはこいつ一択だろう
エンジニア時代はVivaldiを使ってたけど、ちょっと重たいのとエンジニアを辞めてからや安定感を求めてChromeに戻った
ちなみに、検索エンジンは今はGoogleを使ってるけど、DuckDuckGoに興味津々(2020年1月現在)

### Google 日本語入力
こいつがないと話にならん
予測変換はこいつに任せる

### HyperSwitch
必須アプリ
ctrl + tab によるアプリを移動するときに、初期状態だと一つのアプリ内で複数のページを開いてる時（ChromeやVSCodeなど）にどちらのウィンドウを開くか指定できない（自動で最新のウィンドウを選択してくれる）
こいつがいると、その煩わしさから開放される

### iTerm2
macユーザー必須のターミナルアプリ
シェルはfish一択だろう
zshとは全然友達になれなかった
fishはその名の通り、良い友になってくれる


### KeyboardCleanTool
キーボードのお掃除アプリ
使う機会は多くはないけど、たまに欲しくなる。
とりあえず入れておくと良いだろう

### Monosnap
画面キャプチャしてすぐに編集するときには便利
使い勝手は良い

### Poster
instagramをPC上で操作するアプリ
常時起動してるけど、使い勝手は良くない
もっと良いアプリがあったら教えてほしい

### Simplenote
メモアプリ
Android、iPhone、PC、Webとどこでも共有できるのでめちゃ便利
必要最低限の機能のみしかない点が、シンプルだなぁ〜と思わせる

### Slack
言わずとしれた、コミュニケーションツール
エンジニア時代は会社用のグループがあり、必須アプリだった
勉強会でもグループを作ることがしばしばあった。
最近だとオンラインサロンとかで使ってるところもあるような？

### Spectacle
必須アプリ
画面分割のショートカットを自由に決められる
作業効率化アプリの王道

### The Unarchiver
解凍ソフト
とりあえずこいつに投げておけば問題ない

### Tunnelblick
VPN接続アプリ
海外にいることが多い僕にとっては、アマゾンプライムを視聴するために必須のアプリである

筑波大のVPN Gate(https://www.vpngate.net/ja )も合わせて



### Visual Studio Code (VSCode)
エディタはこいつ一択だろう
統合開発環境とかは重すぎて使えない
必要なプラグインを必要な分だけ入れるのがスマートだと思っている

### Wondershare Filmora
動画編集ソフト
全然使ってないから知らんｗ
けど、いつか使うだろうと思っている。。

### XMind
ほぼ使ってない
マインドマップを作成するならこいつ一択らしい
使う機会がいつか訪れると思っている

### zoom.us
skype
なんか使う機会が多かったから入れてた
けど今はパタンとなくなった
そろそろアンインストールの時期か、、


## おわりに
今回は備忘録として僕のパソコンに入ってるアプリを全て書き残した。
なにかの参考になれば嬉しいな

次はGoogle拡張一覧でも書き残そうかなと思う

ではまた
ターミナルで入力中の文字を一発でまとめて削除する方法の備忘録

## はじめに
ターミナルでコマンドを入力中に「あ、ミスった。。deleteボタン連打するのか、、、」ってなった事ありませんか？
今回はそんなときに使えるTipsを共有したいと思います。

## 入力中の文字列を一括削除
ズバリ、ターミナルで入力中の文字列を一括削除するコマンドは以下の通りです。

```
ctrl + u
```

マジで一瞬、、、

もっと早く出会いたかった。。。

## おわりに
今回は以上です。

今までどれだけの時間を無駄にしていたか。

これで今後の作業効率が格段に早くなることでしょう。

では、またね
ハンバーガーメニュー作成の考え方

# パターン1 本文に重ならない

##方針

ハンバーガーを押したら本文が左にずれて、メニューが現れるようにする。
メニューを配置して、本文を上から被せる。(z-index)

## Step1 メニューを配置する

- 右上に配置するのでtopとrightを0にする

## Step2 本文を配置する

- **メニューの上に配置するのでz-indexの値をメニューより大きくする**
- ハンバーガーを右上に設置する
- 左上に配置するのでtopとleftを0にする


## Step3 javascript実装

javascriptで本文を左にずらすのではなく、cssで左にずらす。
javascriptの役割はイベントをきっかけにcssを適用すること。

- cssでopenというクラス名にして、`left: -○○px;`と記述
- ハンバーガーにクリックイベントを記述し、上記のopenクラスを本文に着け外しする。

# パターン2 本文に重なる

##方針
ハンバーガーを押したら本文にマスクがかかり、左からメニューが現れるようにする。
本文、マスク、メニューの3重構造にする。
メニューには閉じるボタンをつける


## Step1 メニューを配置する

- 画面外に配置させたいので、rightでマイナス○○pxに設定する。
- 上記のままだとスクロールしたら見えてしまうため、overflow-xプロパティでhiddenを設定する。

## Step2 メニューが閉じているときのスタイルを記述

- 本文の上にカバー、カバーの上にメニューが来るのでそのようにz-indexを設定
- カバーは最初はdisplayをnoneにしておく
- 画面いっぱいに広がるようにcssを書く


## Step3 メニューが開いてるときのスタイルを記述

- カバーのdisplayをnoneではなくblockにする
- メニューのrightをマイナス○○pxではなく0pxにする

## Step4 javascript実装

- ハンバーガーにクリックイベントを設置し、メニューが開いてるときのクラス名を付与
- 閉じるボタンにクリックイベントを設置し、メニューが開いてるときのクラス名を削除
iframe内のスクロール量とラジオボタンの活性制御を連動させる

## iframeで表示したテキストファイルのスクロール量とラジオボタンの活性制御を連動させる
　利用規約なんかでよくある、「ちゃんと最後までスクロールしないと【同意する】にチェックできないよ！」的なものを作ったので備忘録として。

```html:sample.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Sample page</title>
  </head>
  <body>
    <iframe id="iframeTxt" src="/images/textfile.txt"></iframe>
    <input type="radio" name="agreeOrNot" value="1">はい
    <input type="radio" name="agreeOrNot" value="0" checked="checked">いいえ
    <input type="hidden" id="radioCtlFlg" value="0">
  <script>
    var iframe = document.getElementById("iframeTxt");
    var radio = document.getElementsByName("agreeOrNot");
    var radioCtl = document.getElementById("radioCtlFlg");

    // スクロール量チェック
    function textReadChk() {
        // 既読判定
        if (radioCtl.value == "1") {
            return;
        }

        // テキスト全体の高さ
        var scrollHeight = iframe.contentDocument.documentElement.scrollHeight;
        // スクロール量
        var scrollAmount = iframe.contentWindow.pageYOffset;
        // ウィンドウ（表示される枠）の高さ
        var windowHeight = iframe.contentWindow.innerHeight;

        if (scrollAmount + windowHeight === scrollHeight) {
            for (i = 0; i < radio.length; i++) {
                radio[i].disabled = false;
            }
            radioCtl.value = "1"
        }
    }

    // iframe内のscrollイベントを親フレームで取得し、チェック呼び出し
    document.addEventListener('DOMContentLoaded', function() {
        iframe.addEventListener('load', (function(element) {
            return function() {
                element.contentWindow.addEventListener('scroll', textReadChk);
            };
        })(iframe, false);
    }, false);
  </script>
  </body>
</html>
```

### 参考にさせていただいたサイト
[Javascript（jQuery）でiframe内のイベントを親フレームで取得する：綺麗に死ぬITエンジニア](https://s8a.jp/javascript-event-iframe)
AppStore への iOS アプリ登録 リジェクト事例(2020/1)

AppStoreへのアプリの更新申請でリジェクトとなった事例をご紹介します。

# リジェクト内容
```:From&nbsp;AppStore&nbsp;審査チーム
メタデータ却下済み
```
今回、iPhone X 向けのレイアウト調整を行ったので、その旨をAppStore Connectの **このバージョンの最新情報** に記載したのですが。。。

# リジェクト原因
```:このバージョンの最新情報への記載内容抜粋
・IPhneX向けに表示の調整を行いました。
```
おはずかしいことながら、 **iPhone X** のつづり違いが今回のリジェクトの原因でした。
このあたりはどちらかというと、ロボットでチェックされているのかもしれないなと思いました。

# まとめ
プロダクト名のつづりを直して再申請したら無事審査を通過しましたが、こういったちょっとしたミスで再申請が必要になり、審査通過までにまた数日を要することになります。  
更新内容にプロダクト名等の固有名詞が入る場合には、申請ボタンを押す前に、一度立ち止まって初歩的なミスがないか確認するようにしましょう。
DynamoDBのチュートリアルを読み解く【5: トリガーを作成してテストする】

# はじめに

今回も[DynamoDBのチュートリアル](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Streams.Lambda.Tutorial.html)を実施しました。
最後はLambdaでのトリガー作成です。

（バックナンバー）
[1.DynamoDB テーブル作成](https://qiita.com/kkino1985/items/f9091c1e1efb35009ce7)
[2.IAMポリシー/ロール作成](https://qiita.com/kkino1985/items/a087bc3077f36b4b1e21)
[3.SNSトピック作成](https://qiita.com/kkino1985/items/f40d950cfd6d464635fa)
[4.Lambda関数作成/テスト](https://qiita.com/kkino1985/items/4850f4bba7b8418c2cc8)

# 5.1 トリガー作成用ARN情報取得

今回はDynamoDBのレコードに新しい項目が追加された場合にSNSトピックを発行するようにしたいので、LambdaのイベントソースとなるDynamoDBのトリガーを作成する必要があります。
まずはそれに必要なDynamoDBのARNを取得します。

    aws dynamodb describe-table --table-name BarkTable


## --table-name

- 書式 : --table-name `(value)`

情報を表示するテーブル名。


## 出力結果

コマンドを実行すると最後の方にARNが出力されます。

        ...
        },
        "LatestStreamLabel": "2020-01-28T17:05:04.444",
            "LatestStreamArn": "arn:aws:dynamodb:region:xxxxxxxxxxxx:table/BarkTable/stream/2020-01-28T17:05:04.444"
        }
    }


# 5.2 トリガー作成

    aws lambda create-event-source-mapping \
        --region us-east-1 \
        --function-name publishNewBark \
        --event-source streamARN  \
        --batch-size 1 \
        --starting-position TRIM_HORIZON

※Lambda作成時と同じオプションの説明は省略します
※--regionは例によって自分の環境に置き換えましょう

## --event-source

- 書式 : --event-source `(value)`

イベントソースのARNを指定。
公式リファレンスでは`--event-source-arn`となっていますが、両方試したところこれはどちらでも実行可能のようです。

## --batch-size

- 書式 : --batch-size `(value)`

単一のバッチで取得するアイテムの最大数。
今回の場合で言うと、DynamoDBへの書き込み→SNS実行を最大でまとめていくつ実行するかの設定。

通常は負荷テストをしながら適切な値を設定する（でないとスループットが死ぬ）のですが、今回はチュートリアルで且つ手動書き込みなので1件で十分です。

## --starting-position

- 書式 : --starting-position TRIM_HORIZON or LATEST or AT_TIMESTAMP

読み取りを開始するストリーム内の位置。（DynamoDB Streamsかkinesisのみ使用可能）
各オプションの効果については下記の通り。

- TRIM_HORIZON : 読み取りされていないものを古い順から読み取る。先入れ先出し（FIFO）
- LATEST : 最新のものから順番に読み取る。後入れ先出し（LIFO）
- AT_TIMESTAMP : 指定時刻以降のものを読み取る（--starting-position-timestampで開始時刻を指定。kinesis専用）

# 5.3 トリガーのテスト

テストとして、DynamoDBにアイテムを追加してみます。
（実行してメールが飛んでくれば成功）

    aws dynamodb put-item \
        --table-name BarkTable \
        --item Username={S="Jane Doe"},Timestamp={S="2016-11-18:14:32:17"},Message={S="Testing...1...2...3"}


## --table-name

- 書式 : --table-name `(value)`

追加するアイテムを格納するテーブル名。

## --item

- 書式 : --table-name `(attribute)`={`(datatype)``(value)`} or JSON

追加するアイテムを格納するテーブル名。データ型（datatype）の「S」は文字列の意味。
また、JSON形式で書いたファイルを`file://item.json`のように指定する方法もある。

（当然ながら）主キーは必ず含めないといけないので注意。

# 終わりに

亀のごときスピードでしたが、これにてDynamoDBのチュートリアルは終わりました。
感想としては、基本的なところがまだまだ全然把握できていないな、というところでした。

他のチュートリアルもやろうかと思っていますが、どこまで記事にするかはちょっと考え中です（マンネリ化しそうなので）

# 参考URL
https://docs.aws.amazon.com/cli/latest/reference/dynamodb/describe-table.html

https://dev.classmethod.jp/cloud/aws/tips-for-dynamodb-streams/

https://docs.aws.amazon.com/cli/latest/reference/lambda/create-event-source-mapping.html

https://docs.aws.amazon.com/cli/latest/reference/dynamodb/put-item.html

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/WorkingWithItems.html
Rest APIで使われるHTTPメソッドとURL設計

# Rest APIで使われるHTTPメソッド

| メソッド名 | 役割 | 冪等性 | 安全性 |
|:-----------------|:------------------|:------------------:|:------------------:|
| GET | リソースの取得 |○|○|
| POST | リソースの作成 |×|×|
| PUT | リソースの全体更新（置き換え） |○|×|
| PATCH | リソースの部分更新（一部を更新） |×|×|
| DELETE | リソースの削除 |○|×|

冪（べき）等性とは同じ操作を1回行っても何回行っても同じ結果が得られる性質のことです。
同じ結果というのは、リソースの状態のことで、HTTPレスポンスのことではありません。
例えば、DELETEメソッドは冪等ですが、通常1回目のリクエストでは200レスポンスを、2回目以降のリクエストを404レスポンスを返します。
このようにレスポンスは異なるものの、リソースの状態は同じです。（特定のデータが消えた状態は同じ）
上記の役割や冪等性はあるべき論です。
意味としては正しくないですが、GETでリソースを変更しようと思えば可能です。

PATCHは冪等ではありません。ただし冪等であるように振る舞うことは可能です。

>HTTP PUT メソッドは、ドキュメントの完全な置換のみを許可します。PUT とは違って、PATCH はべき等ではありません。すなわち、同一の PATCH リクエストが連続しておこなわれると、異なる効果が得られる可能性があります。ただし PATCH リクエストがべき等になるようにリクエストすることは可能です。
[PATCH \- HTTP \| MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/Methods/PATCH)

安全性はリソースの変化が生じるかどうかを示す性質です。
上記ではGET以外はリソースの変更が生じるので安全ではありません。

以下に各メソッドの役割を記載しますが、どのHTTPメソッドを使うべきかは、解釈が分かれます。
先述のとおり、あるべき論なので。

下記URLにあるようにメソッドの種類によって、同じURLでも違う処理を割り当てることが可能です。

## GET

GETメソッドはリソースの取得に用いられます。
ブラウザではページの表示によく使用されます。

考えられる処理:

- データ取得
- ファイルダウンロード

URL例:

- /api/users
- /api/users/:user_id
- /api/users/:user_id/photos

## POST

リソースを作成するメソッドです。
リソース名がサーバーで割り振られてデータができます。
ブラウザではフォームデータ送信によく使用されます。

考えられる処理:

- データ新規作成

## PUT

リソースの全体更新（まるごと置き換え）を行うメソッドです。

考えられる処理:

- ファイルアップロード（ファイルがなければ新規アップロードされて、あれば上書きされる）

URL例:

- /api/users/:user_id/photos/:photo_id
- /api/articles/:article_id/bookmark

## PATCH

リソースの部分更新を行うメソッドです。

Railsでは更新をかけるたびに、updated_at（レコード更新時刻）は変更になるので冪等ではないPATCHメソッドを採用しているとされています。

> Now let’s think about ordinary edit forms in typical Ruby on Rails applications. How many times are we sending a complete representation for replacement? Not always, perhaps we could say that it is even rare in practice that you do so. For example, the conventional created_at and updated_at timestamps normally can’t be set by end-users, though they are often considered to belong to the representation of resources that map to records.

この理屈でいくと他のウェブアプリケーションフレームワークの更新処理もPATCHになるでしょう。

考えられる処理:

- データ更新

URL例:

- /api/users/:user_id

## DELETE

リソースを削除するメソッドです。

考えられる処理:

- データ削除

URL例:

- /api/users/:user_id
- /api/articles/:article_id/bookmark
試験仕様・異常系について

正直ストレス発散なので色々と異論は認める。

# テストの目的とは何か
異常が無い事の証明
では無い。

- 機能要件、非機能要件の定義
- 条件や手順の明示

である。


# 異常の種類について
- 正常系
    - コレが出来てないと論外な範囲
    - 機能要件
- 純正常系
    - 食らっても耐えられる範囲
    - 入力ファイルが無い、形式がおかしい等
- 純異常系
    - 外部の手が入れば直ぐ復旧出来る範囲
    - HDDがパンパン、足引っ掛けてLAN抜けた等
- 異常系
    - どうしようもない諦め安定な範囲
    - 地震雷火事物理


# キレる条件
- `正常である事を確認`としか無い
    - 何が異常なのか分からないのでメクラ
- `更新される事を確認`としか無い
    - `何を以て更新とするか`が無いとメクラ
- 条件だけで手順が書いてない
- `コマンドで確認`とあるがどこにも書いてない
- `別資料参照`とあるが見付からない
- `バッチで確認`とあるが見付からない
- 旧版の資料を流用してるがメンテされてない
- エクセル
- 自動化出来る項目を目視で確認
- テスターに依存する
- `前工程でテスト完了してる`と渡されてテストデータが無い
- 人間を信用している
- 解読不能
- 同一項目で矛盾する
- テスト内容が整理されていない
- `コマンド`-`環境変数`-`設定ファイル`の順に記述されてて順番がおかしい
- `正常系で最後まで`-`後ろの方の異常系`-`手前の異常系`のように逆順にやった方が楽
- 再現性が無い
- 印刷前提


# とりあえず
再現性の無いテストは無いよりマシ、もといやって安心するのでやらない方がマシな認識。
C#のTypeForwardedToについて調べた

# はじめに

`System.Runtime.CompilerServices`に定義されている`TypeForwardedFromAttribute`

とは何なのか、なんとなく調べた。

"なんとなく"というのは何とはなしにということではなく、あいまいに調べたということです。



# 一言でいうと

アセンブリ内に定義された型情報を別のアセンブリに転送するしくみ。



# 書いてみた

実行環境は`Visual Studio 2019/.NET Framework 4.7.2`

こういうクラスライブラリ(アセンブリ)があったとする。

```C#
// AssemblyA.dll

using System;

namespace Foo
{
    public static class Bar
    {
        public static void Print() => Console.WriteLine("I'm Bar A.");
    }
    
    public static class Baz
    {
        public static void Print() => Console.WriteLine("I'm Baz A");
    }
}
```

こういう風に使ったとする。

```C#
// Program.exe

namespace ForwardToTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Foo.Bar.Print();
            Foo.Baz.Print();
        }
    }
}

```

`Program.exe`を実行すると当然こう出力される。

```
I'm Bar A.
I'm Baz A.
```

ここで、`Foo.Bar`クラスを`AssemblyA.dll`から別のアセンブリ（`AssemblyB.dll`）に移動させたい気分になったとする。

まず`AssemblyB`に次のように書く。

```C#
// AssemblyB.dll

using System;

namespace Foo
{
    public static class Bar
    {
        public static void Print() => Console.WriteLine("I'm Bar B.");
    }
}
```

`AssemblyA`プロジェクトに`AssemblyB`への参照を追加し、`AssemblyA`を次のように修正する。

```C#
// AssemblyA.dll

using System;

// TypeForwadedToによりAssemblyBのFoo.Barへ転送される
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Foo.Bar))]

namespace Foo
{
    public static class Baz
    {
        public static void Print() => Console.WriteLine("I'm Baz A");
    }
}
```



`AssemblyA`をリビルドし、`AssemblyA.dll`,  ``AssemblyB.dll``を`Program.exe`のあるディレクトリにコピーする。

`Program.exe`を実行すると出力が次のように変わる。

```
I'm Bar B.
I'm Baz A.
```

# 何のためにあるのか？

[ドキュメント](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.typeforwardedtoattribute)によると

> Use the TypeForwardedToAttribute attribute to move a type from one assembly to another without disrupting callers that compiled against the old assembly.

古いアセンブリでコンパイルされた`caller`(上の例では`Program.exe`)に手を加えず、型を別のアセンブリに移動できる。

ライブラリ製作者はとてもありがたいのだと思う。


# TypeForwadedFromもある

どのアセンブリから転送される可能性があるのか、メタ情報として持たせることができる。

上の`AssemblyB`だと

```C#
// AssemblyB.dll

using System;

namespace Foo
{
    [System.Runtime.CompilerServices.TypeForwardedFrom("OldAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")]
    public static class Bar
    {
        public static void Print() => Console.WriteLine("I'm Bar B.");
    }
}
```

こういうふうに属性をつける。

適当な文字列を書いてもビルドエラーやランタイムエラーになったりはしないようなので、ただのメモみたいなもの・・・？



# 備考

* はじめ`.NET Core`でやろうとして、ただdllを置き換えただけでは`Program.exe`が`AssemblyB`の参照エラーで起動できなかった。なぜ`.NET Framework`では参照できるのか全く理解していない。
* 初投稿なのに地味。反省したい。




# 参考にしたもの

https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.typeforwardedtoattribute

https://ufcpp.net/study/csharp/package/typeforwarding/
「手っ取り早くWebフロントエンド + WebAPIを作り始める方法2020」メモ

思いついたまま書き連ねたメモです。
「これが唯一の正解」だとは全く考えてなくて、あくまで「便利な方法の1つ」として書いてます。
機会あれば清書したい。

動機: https://twitter.com/mazgi/status/1223273907555192832

サンプル+説明 :arrow_double_down: 

- [Web Frontend を Next.js + TypeScript で書いて Docker でビルドする](https://mazgi.github.io/posts/2020.01/dockerized-typescript-nextjs-project/)
- [Web APIをTypeScriptで書いてDockerでビルドする](https://mazgi.github.io/posts/2020.01/dockerized-typescript-webapi-project/)

関連: https://qiita.com/mazgi/items/6892d3f5e8223fd6ba6b

## 開発環境: Linux + [Docker](https://www.docker.com/) + [Docker Compose](https://docs.docker.com/compose/)

ローカルでもクラウドでも良いのでLinuxマシンを1つ用意して `docker-compose up` するのが楽です。
本番環境がKubernetesなどのコンテナプラットフォームなら開発も本番もDocker上で済むのは開発→本番差分でのハマりも軽減できて特におすすめです。
(気軽にローカルファイル読んでたとか、本番はフロントエンドとWeb API分かれるのにいつの間にか同じホストで動く前提の構成になってたとか、 etc.)

「Docker Compose初めて」って方は :arrow_double_down: が参考になれば幸いです。
`docker-compose.yml` と `Dockerfile.d/*` コピーすれば割と動くはず。

- https://github.com/mazgi-showcase/202001.nextjs-typescript-jest/blob/bc32494ed41b3c46ff969bc8230e32be0f1399c8/docker-compose.yml
- https://github.com/mazgi-showcase/202001.express-typescript-build-with-webpack/blob/428f70743979a072e8ff107aa75e3bc7297c8209/docker-compose.yml

気をつけることとしては **Docker for Macは劇重いのでLinuxを用意しましょう！**
どうせ本番はLinuxかコンテナで動かすでしょうし。

## 開発言語: [TypeScript](https://www.typescriptlang.org/)

ブラウザがしゃべれるのはあくまでJavaScript(ECMAScript)なので、Web開発である以上JavaScriptから遠く離れるのは難しいのが実情です。

好みや慣れによりますが、個人的にはWebフロントエンドからWeb APIまでは同じ言語で書いてしまった方がコンテキストスイッチ少なくてやりやすいと感じます。
Web API、とくにBFFと呼ばれるやつは「Webフロントエンドに向けて機能を提供する」のが仕事だと思ってるのでWebフロントエンドと並行して開発する際の効率を重視したいです。

しかし型がほしい、型の有無は開発規模膨らんだり保守フェイズで効いてきます。

TypeScriptは型ありかつ変換(Transpiling)でJavaScriptになるので型を使った堅い開発とブラウザでの動作を両立することができます。

ただ、JavaScriptへの変換にまつわるトラブルに遭遇することはあります。。

# フレームワーク/ライブラリ

## [React](https://reactjs.org/)

シェアが大きく、当面廃れなさそう。
(Vueなども良いものだと思います)

TypeScriptフレンドリー。

ソフトウェアエンジニア視点ですが、特にMaterial UIなどのCSSフレームワークを導入するとHTMLやCSSをあまり書かずに開発できるので、「アプリケーション開発」の感覚でWebフロントエンドを書きたい場合やHTML/CSSにあまり慣れていない場合は便利だと思います。

一方で元のスキルセットがWebデザイン寄りな方はCSSフレームワークなどの組み合わせによっては「普通にHTML/CSS書きたい！」とフラストレーション溜まってしまうかもしれません。

## [Next.js](https://nextjs.org/)

React単体でWebフロントエンドの開発はできる、しかし開発時や運用でもサーバー立てたいケースはままある、そんな時webpackやbabelを学んだりloaderなどの選定をしたくない。

初学者にとって「Webフロントエンドを本番向けにbuildする」ハードルはそれなりに高い(高かった)。
繰り返しますけどwebpack, babel, 各種loaderやpluginなどの選定や設定は本来リソースを割きたくないにも関わらずとても時間がかかります、数日ハマって成果がxxx.json or xxx.js数行変更なんてザラです(ハマりました)。

プロジェクトのディレクトリやコンポーネントの構成、開発用/本番用のビルドパイプライン設計なども煩わしく感じることがある。

Next.jsはこれらをwrapしてくれて構成もお仕着せてくれる。
(Nuxtなども良いものだと思います)
Next.jsのレールに乗ってプロジェクトをスタートするのは1つの良い始め方だと感じた。

## [GraphQL](https://graphql.org/) + [TypeGraphQL](https://typegraphql.ml/) + [TypeORM](https://typeorm.io/#/)

GraphQL自体はフレームワークでもライブラリでもないが、体験の良さはライブラリである TypeGraphQLとTypeORMに支えられている部分があるのでここに記載。

RESTも良いですが、開発初期によくある「RDBMSのエンティティをほぼそのままWeb APIで返す」ようなフェイズでGraphQLとこれらのライブラリは開発体験がよかったです。

こんな感じでアノテーション付けてエンティティを書けるのでエンティティ設計するだけでDBスキーマ生成とGraphQLスキーマ定義の両方が済んで楽、少なくとも初期は。
https://github.com/mazgi/console/blob/b86c5367366dbec658a6e8b6c467ec215ee379d1/bff/src/entities/User.ts

高負荷なシステムや長期運用はまだ未知です。
何使ってもスキーママイグレーションとキャッシュがつらそうですね。。
nullableなMono/Fluxの後続処理を分岐する（WebFlux基本編）

[前回の記事](https://qiita.com/kilvis/items/fb18be963da6cac03ee9)でWebFluxの基本的なAPI呼び出しの順次・並列の実装例を紹介しました。
今回は**nullableなMono/Fluxの後続処理**として、nullの場合／nullじゃない場合の分岐の実装例を紹介します。

# まずは分岐を考えない場合の実装
前回の実装例です。
![activity_1-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430093/45908bc9-fcc9-0e69-5b3c-f2f2e77d2e21.png)

```java:順次処理（nullの考慮なし）
  @GetMapping("/headers/find-by-user/{userId}")
  public Mono<HeadersByUser> findHeadersByUserId(@NotNull @PathVariable final String userId) {
    return userService.read(userId)  // 1
        .flatMap(user -> headerService.findByUserId(user.getUserId())  // 2
            .collectList()
            .map(headers -> HeadersByUser.builder()  // 3
                .user(user)
                .headers(headers)
                .build()));
  }
```

1. IDからユーザを取得
2. 紐付く記事ヘッダ一覧を取得
3. 最後にDtoに詰めて返す

上の実装は `1.ユーザを取得できない場合(=null)` の考慮が漏れています。
そこでリファクタしたいのですが、**以下のように実装するのは誤りです。**

```java:誤ったnullの後続処理
  @GetMapping("/headers/find-by-user/{userId}")
  public Mono<HeadersByUser> findHeadersByUserId(@NotNull @PathVariable final String userId) {
    return userService.read(userId)  // 1
        .flatMap(user -> {
          if (user != null) {
            // UserがあるならDtoに詰めて返す
            return headerService.findByUserId(user.getUserId())  // 2
                    .collectList()
                    .map(headers -> HeadersByUser.builder()  // 3
                        .user(user)
                        .headers(headers)
                        .build());
          }
          else {
            // Userが無ければ空のDtoを返す（※誤り）
            return Mono.just(HeadersByUser.builder().build());  // 2'
          }
        });
  }
```

一見すると問題なさそうに見えますが、
**ユーザが取得できない場合 `.flatMap(user -> ...)` には処理が入らずスキップされます。**

そのため`else { ... }` はデッドコードとなり、**空のDtoを返すことはできません。**


# nullの後続はswitchIfEmptyで処理する
ユーザが取得できなかった場合（Mono&lt;empty&gt;）の後続処理は`switchIfEmpty`に書きます。

```java:正しいnull（empty）の後続処理
  @GetMapping("/headers/find-by-user/{userId}")
  public Mono<HeadersByUser> findHeadersByUserId(@NotNull @PathVariable final String userId) {
    return userService.read(userId)  // 1
        .flatMap(user -> headerService.findByUserId(user.getUserId())  // 2
            .collectList()
            .map(headers -> HeadersByUser.builder()  // 3
                .user(user)
                .headers(headers)
                .build())
        )
        .switchIfEmpty(
            Mono.just(HeadersByUser.builder().build())  // 2'
        );
  }

```

これでユーザが取得できた場合／できなかった場合とで、後続の処理を分岐することができます。


# 今回のまとめ
先行のMono/Fluxがnull(empty)の場合

- 後続の`map`や`flatMap`には処理が入らない
- 何か処理したい場合は`switchIfEmpty` に書く

となります。
知っていればなんてことはありませんね。
タブ切り替えの考え方

# タブ切り替えを構成する要素

- 切り替えタブ
    - liタグで表現
- タブごとの内容
    - 特にタグの指定は無し

# Step1 切り替え前のHTMLを書く

- 切り替えタブは横並びで記述
    - 親ノードにdisplayをflexに指定
    - 内容ごとのidとリンクするようにデータ属性を付与
- タブごとの内容
    - divタグなどで記述
    - 内容ごとにidを付与

# Step2 選択されているタブと選択されていないタブにCSSをそれぞれ適用

- 選択されているタブに対してCSSを適用
    - クラス名をactiveとして、何らかの色などをつける
    - コンテンツ部分を整える
- 選択されていないタブに対してCSSを適用
    - クラス名をhiddenとして、displayをnoneにする
    - マウスを置いたときに押せる感を出す
        - aタグはホバーしたときにマウスがポインターになる
        - ホバーしたときに透明になるようにする
            - 例:`opacity: 0.5;`

# Step3 javascript実装

- タブ全てにクリックイベントを設定する
    - タブ
        - aタグの規定の動作(ページ遷移)をキャンセルする(preventDefault)
        - 全てのタブからactiveクラスを削除(forEach,classList.remove)
        - クリックされたノードにactiveクラスを付与(classList.remove)
    - 内容
        - 全ての内容からactiveクラスを削除(forEach,classList.remove)
        - クリックされたノードのデータ属性の値を取得し、activeクラスを付与(getElementById,classList.add)
Ruby 2.7 とRails 6.0.2.1でrailsコマンドをたたくとすごいwarningがでる

Ruby2.7とRails6.0.2.1とpostgres12.1でDBを作るために以下のコマンドをたたくとすごいwarningがでた（処理は完了している）のでwaringを表示しないようにしてみる。

```console
$ bundle exec rails db:create
/.rbenv/gems/2.7.0/gems/actionpack-6.0.2.1/lib/action_dispatch/middleware/stack.rb:37: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/.rbenv/gems/2.7.0/gems/actionpack-6.0.2.1/lib/action_dispatch/middleware/static.rb:110: warning: The called method `initialize' is defined here
/.rbenv/gems/2.7.0/gems/activerecord-6.0.2.1/lib/active_record/type.rb:27: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/.rbenv/gems/2.7.0/gems/activerecord-6.0.2.1/lib/active_record/type/adapter_specific_registry.rb:9: warning: The called method `add_modifåier' is defined here
/.rbenv/gems/2.7.0/gems/activemodel-6.0.2.1/lib/active_model/type/integer.rb:13: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/.rbenv/gems/2.7.0/gems/activemodel-6.0.2.1/lib/active_model/type/value.rb:8: warning: The called method `initialize' is defined here
/.rbenv/gems/2.7.0/gems/activerecord-6.0.2.1/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb:12: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/.rbenv/gems/2.7.0/gems/activemodel-6.0.2.1/lib/active_model/type/value.rb:8: warning: The called method `initialize' is defined here
Created database 'XXX_development'
Created database 'XXX_test'
```

Ruby2.7にまだ対応していないgemがあるからでているっぽい。
一旦warningがでないようにはbashprofileに環境変数を追加する。

```console
$ vi ~/.bash_profile
```

で`bash_profile`を開いて`export RUBYOPT='-W:no-deprecated -W:no-experimental'`を追加するとエラーはでなくなる。

## 参考

https://stackoverflow.com/questions/59491848/how-to-fix-railss-warning-messages-with-ruby-2-7-0
https://tech.recruit-mp.co.jp/server-side/post-19932/
【React】Omise.jsでカード情報を登録

こんにちは。
ECサイトを開発していて決済を実装するために[Omise](https://www.omise.co/ja/docs/japan)というapiを使う機会がありました。
カード情報を登録するにあたってReactで実装するのに苦労したのでメモです。
フォームはドキュメントにサンプルがあったので参考にしてみてください。

#Omise.jsとは
Omise.jsはクライアントサイドのJavaSciprtライブラリで、クレジットカードの情報をトークン化するために利用します。 ユーザーが決済フォームへ情報を入力し、フォームのSubmitのタイミングでOmise.jsが決済フォームの情報をOmiseのサーバーへ送信し、トークンを作成して取得します。取得したトークンは、あなたの決済フォームで送信されますので、あなたのサーバーサイドでは、そのトークンを利用し決済の処理を実行できます。引用：https://www.omise.co/ja/collecting-card-information/japan

#使い方
まずindex.htmlファイルのbodyタグ内に下記を追加します。
jQueryが必要だったり、Omise.jsからOmise APIに対して認証ができるように自身のパブリックキーを追加します。

```HTML
<script src="https://cdn.omise.co/omise.js"></script>
<script src="http://code.jquery.com/jquery-1.12.1.min.js"></script>
<script>Omise.setPublicKey("発行したパブリックキー");</script>
```

#トークン作成
カード情報の送信先がサーバーへ行かないようにします。

```React:Card.js
class Card extends React.Component {
  constructor() {
  super();
  this.state = {
    params: {
        name: '',
        number: '',
        expiration_month: '',
        expiration_year: '',
        cvc: ''
      },
    omiseError: "",
  }
  
  handleSubmit = () => {
    let card = {
      name: this.state.params.name,
      number: this.state.params.number,
      expiration_month: this.state.params.expiration_month,
      expiration_year: this.state.params.expiration_year,
      security_code: this.state.params.cvc
    };
    this.postOmise(card);
  };

  postOmise = card => {
    const { Omise } = window;  // この記述がないとOmiseを読み込めずエラーになる。
    Omise.createToken('card', card, (statusCode, response) =>
      this.editCardToken(response)
    );
  };

  editCardToken = response => {
    if (response.object === 'error') {
      this.setState({
        omiseError: '無効なカードです。入力内容を確認してください。'
      });
      return;
    }
    if (!response.card.security_code_check) {
      this.setState({ omiseError: 'セキュリティコードが違います' });
      return;
    }
    this.createCard(response);
  };

  //userテーブルのtokenカラムにid（テスト環境では"cust_test_hogehoge"が返ってくる）を保存
  createCard = (response) => {
    axios
      .post('/card_token', {
        token: response.id
      })
      .then(() => {
        window.alert(`カード情報を登録しました！`);
      })
      .catch(error => {
        console.log(error);
      });
  };
}
```

JavaScriptのドキュメントを参考にしながら、実装したけどOmiseが読み込めなかったり、コールバックでthisが使えなくなったりして、非常に詰みましたが、関数をうまく切り分けてなんとか行けました。
あとは、名義人、カード番号、有効期限の月、年、セキュリティコードのフォームを作成し、登録ボタンを押したタイミングでhandleSubmitが動くようにすれば、カード情報の登録ができるようになると思います。
後日、決済についてはGoの処理を書く予定です。
Git hub 過去のコミットのバージョンに戻したい時

まずターミナルで`$ git log`を使用し戻りたいcommit idを探す
次に`$ git reset --hard コミットID`をする事でローカル上でその状態に戻る事ができる
しかしこのままではリモートの方ではgit reset --hardされてないので
 `$ git push origin master -f`をする事で
今のローカルの状態を強制的に反映させることができます。

これでOK
AtCoder に Cython がやってくる!!

# AtCoder に Cython がやってくる!!

[言語アップデート](https://twitter.com/atcoder/status/1223141026707017728)クルーってことで、新ジャッジテスト環境で Cython が試せたので触ってみました. ちなみに Cython に触るのは初めてで何もわかっていません(笑)

ちなみに言語アップデート後の Python 3 は 3.8.0 で、Cython は 0.29.14 です.

## Python のコードはそのまま動く

ABC085C - Otoshidama で試してみました. Python 3 で 678 ms で AC したソースコードをそのまま Cython で提出したら 614ms で AC しました.

```python
from sys import exit

N, Y = map(int, input().split())

for i in range(N + 1):
    for j in range(N + 1 - i):
        k = N - i - j
        if 10000 * i + 5000 * j + 1000 * k == Y:
            print('%d %d %d' % (i, j, k))
            exit()
print('-1 -1 -1')
```

## シンタックスエラーが RE ではなく CE になる!

しょぼい typo をしてもコンパイラ勢は CE でノーペナなのに、Python 3 は RE でペナルティーでずるいと思っていませんでしたか? Cython は CE になります. やったね!!

試しに最初の代入文の = を消すとこんな感じのエラーになります.

```
Error compiling Cython file:
------------------------------------------------------------
...
from sys import exit

N, Y  map(int, input().split())
     ^
------------------------------------------------------------

Main.py:3:6: Syntax error in simple statement list
./Main.c:1:2: error: #error Do not use this file, it is the result of a failed Cython compilation.
    1 | #error Do not use this file, it is the result of a failed Cython compilation.
      |  ^~~~~
```

## 型付けをすると速い!

Cython のドキュメントを見ると .pyx ファイルを書いてコンパイルして、.py でそれをインポートして使うみたいな感じで2つソースコードが必要そうでどう使えばいいのか最初わからなかったのですが、Cython には Pure Python Mode というのがあって、AtCoder ではこれを使うようです.

結果、107ms で AC. 6倍速くなった!!

```python
from cython import longlong

def main():
    N: longlong
    Y: longlong
    N, Y = map(int, input().split())

    i: longlong
    j: longlong
    for i in range(N + 1):
        for j in range(N + 1 - i):
            k: longlong = N - i - j
            if 10000 * i + 5000 * j + 1000 * k == Y:
                print('%d %d %d' % (i, j, k))
                return
    print('-1 -1 -1')

if __name__ == '__main__':
    main()
```

## Appendix: PyPy 3 だと

言語アップデート後の PyPy 3 は 7.3.0 で、150ms で AC. ただし、消費メモリは Python 3 / Cython の 8.7MB に対し、64MB と大幅アップでした.
Pythonで位置情報データ表示-- 地図表示用ライブラリ（folium）でプロットしてみる --

#１．始めに#
店舗の出店計画時に人の動線を確認したりと何かと活躍の場の多い位置情報。
SNSやIoT機器の普及により、位置情報を分析に活用する機会は増々多くなると思います。

今回は直近で位置情報を地図上にプロットして可視化したいな、という機会があり、
調べてみるとpythonで実に簡単に可視化できるらしいとの事。

今回は国勢調査データを使って以下を試してみました。

- **Jupyter Notebook上への地図表示**
- **位置情報のプロット**
- **ヒートマップの作成**

#２．参考サイトなど#

<dl>
  <dt>・地図表示用ライブラリ(今回は「Folium(v0.10.1)」というライブラリを使用)</dt>
  <dd><a href="https://python-visualization.github.io/folium/index.html">https://python-visualization.github.io/folium/index.html</a></dd>
  <dd><strong><font color="red">※「Folium」はOpenStreetMapで呼び出した地図上で動作するようなので、扱うデータに気をつけてください※<font></strong></dd>
  <dt>・e-Stat:政府統計の総合窓口 (国勢調査データ取得)</dt>
  <dd><a href="http://nlftp.mlit.go.jp/isj/index.html">http://nlftp.mlit.go.jp/isj/index.html</a></dd>
  <dt>・国土交通省位置参照情報ダウンロードサービス (緯度経度情報取得)</dt>
  <dd><a href="https://www.e-stat.go.jp/">https://www.e-stat.go.jp/</a></dd>
<dl>

#３．主なアイテム#

- **folium.Map(params)**：地図を作成する。
- **folium.Popup(params)**：ポップアップを表示する。
- **folium.Marker(params)**：地図上にピンをプロットする。
- **folium.plugins.HeatMap(params)**：ヒートマップ表示用のレイヤーを作成する。

#４．サンプルコード#

●　最初にライブラリをインポートします。

```
import pandas as pd
import folium
```
●　事前に作成したデータセットをロードします。

```
df = pd.read_csv('my_data.csv', encoding='shift_jis')
df.head(10)
```
　　こんな感じのデータセットです。
![my_data.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513505/a9c5b3c1-e765-1eb6-b973-da8dcdf25e61.png)

●　foliumの基本的な用途である、
　　・地図の表示
　　・位置情報のプロット
　　・ポップアップの表示
　　を試してみます。

```
# 地図オブジェクト作成
map = folium.Map(location=[35.710402, 139.810668], zoom_start=18)

# ポップアップの作成(「show=True」で常に表示)
p_up = folium.Popup('東京スカイツリー', min_width=0, max_width=1000, show=True)

# 地図オブジェクトにプロット
folium.Marker(location=[35.710402, 139.810668], popup=p_up).add_to(map)

# 地図表示
map
```
![map.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513505/738a33ba-07fb-81d9-b430-47f63b377eca.png)

●　いよいよ、ヒートマップを表示してみましょう。ヒートマップ表示用のプラグインをインポートします。

```
from folium.plugins import HeatMap
```

●　引数として緯度経度情報を渡すことで、ヒートマップが表示できます。各ポイントの半径（radius）とぼかし量（blur）を調整して多少見栄え良くします。

```
# 地図オブジェクト作成
map = folium.Map(location=[35.710402, 139.810668], zoom_start=5)

# ヒートマップレイヤを地図オブジェクトに追加
HeatMap(df[['緯度', '経度']], radius=5, blur=5).add_to(map)

# 地図表示
map
```
![map.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513505/37a0d42a-a8a1-52b6-d21c-427c3393ad53.png)

●　現状、ただレコード数が多くて密度が濃い場所が赤くなっているだけでデータとしての意味は薄いので、「H22-H27の人口増減数」で重みをつけてみましょう。

```
# 地図オブジェクト作成
map = folium.Map(location=[35.710402, 139.810668], zoom_start=5)

# 引数に人口増減数を追加してヒートマップレイヤを地図オブジェクトに追加
HeatMap(df[['緯度', '経度', '平成22年〜27年の人口増減数【人】']], radius=5, blur=5).add_to(map)

# 地図表示
map
```
![map.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513505/b527f95d-faca-5cdf-314a-1eee8ed0a4da.png)

人口増減数の多い地域の赤みが強くなりましたね。

#５．最後に#

どうでしたか？位置情報データを扱うイメージがついたでしょうか。

レンタサイクル業者がユーザーの乗車距離と乗車形態を分析してバイクレンタル事業に踏み込む判断をしたり、行政がイベント時に動線分析をして避難誘導経路を確保したりと、位置情報データはとても活用領域の広いデータです。

ぜひ、色々と試してもらえればと思います！
TimeSpan.Parseで24時以降の値を変換したい時

# TimeSpan.Parseの使い方
[**TimeSpan.Parseメソッド**](https://docs.microsoft.com/ja-jp/dotnet/api/system.timespan.parse?view=netframework-4.8)はstring型で表した時間の値をTimeSpan型に変換してくれる便利なメソッドです。

このTimeSpan.Parseでは以下の通り値を指定できます。(一部省略)
`[d.]hh:mm[:ss]`
[　]内は省略可能

| 要素 | 説明 |
|:----------------:|:-----------------:|
| d | 0から10675199までの日数 |
| hh | 0から23までの時間 |
| mm | 0から59までの分数 |
| ss | 0から59までの秒数 |

# 24:00以降はParseできない

この説明の通り、要素hhが23を超えてしまうと例外が発生してしまい、TimeSpan.Parseが使えないのです。(知らなかった)

```csharp

// 例外発生
TimeSpan ts = TimeSpan.Parse("30:00");
```

# じゃあどうする

解決策は2つ考えられます。

## 解決策1: 時を24で割って日にちにする

日数も指定できるので、時間を24で割って日にちを割り出してしまうという方法。

```csharp

TimeSpan ts = TimeSpan.Parse("1.6:00");
```

## 解決策2: TimeSpanインスタンス化するときに初期化してしまう

```csharp
TimeSpan ts = new TimeSpan(30, 0, 0);
```

私は解決策2のほうが好きです。
意外と知らない「認証」と「認可(承認)」の違い

2019年9月~29歳で未経験から営業→エンジニアになった私が、
脱未経験エンジニアを目指して、業務の中で学んだこと、会社の業務について書いています。
私の現状については最初の投稿をご参照ください。
[[最初の投稿](https://qiita.com/ssuzuki0301/items/ecfde22d30f8f1032c49)]

会社でふと「認証」と「認可(承認)」というワードが出てきて、
ぼんやりと分かっているつもりで、
いざ説明するとなるとできないし、これは理解していない！ということで調べてまとめて見ました。
OAuthなどを扱うと両方出てきます。

# 「認証」と「認可(承認)」
認証と認可(承認)の２つは密接に関係はしていますが、それぞれ別の概念です。
密接に関係しているので、深く学ぶと難しいのですが、
今回はこの２つの違いについて、簡単に説明して行こうと思います。

# 認証とは
**認証:Authentication**

**「誰であるか」**を確認、実証することです。
実生活でイメージすると身分証明証で「本人確認」をするようなものです。
webの世界だとログインIDとパスワードでどのユーザーかを確認する処理が認証に当たります。
生体認証なんかも「誰か」を確認するものですよね。
iPhoneのFaceIDとか。

# 認可(承認)とは
**認可(承認):Authorization**

英語にするとわかりやすいですが、**権限(Authority)**についての概念。

つまり、**「ある特定の権限を持っているか」**を確認、実証することです。
実生活では、コンビニでお酒を買う際に身分証の提示を求められる行為が認可に当たります。
こちらの身分証提示は認証とは違い「２０歳以上でお酒を購入できる権限を持っているか」の確認です。

役所などに代理手続きに行った際に、委任状の提示を求められるのも、
代理で手続きをする権限があるかの確認で、認可にあたります。

「誰か」ではなく、「権限」があるかです。

webの世界だと、ユーザーに有料コンテンツの閲覧の権限を与えることが例として挙げられます。
「課金をした人のみ閲覧できます」というものですね。

ただ、こういったサービスになると、「誰」で、「閲覧権限を与えるか」という２つの要素が絡み、
認証して、認可という流れになります。

冒頭で述べましたが、別の概念ではありますが、webサービスとなると２つが密接に絡みあって構成される場合があります。

このあたりの内容はまた別で書こうと思います。

# 最後に
とりあえず今回は、
**・認証:「誰であるか」の確認、実証**
**・認可(承認):「ある特定の権限を持っているか」の確認、実証**
この２つの違いを説明しました。

改めて考えてみると当たり前なのですが、
しっかりと、説明しろ！と言われたらパッとできない方もいたのではないかと思います。

ぼんやりと理解していることを明確にして、確実な知識にしていきたいです。

拙い説明でしたが、
最後まで読んで頂きありがとうございました！
Visual Studio 2019へのGoogle Test / Google Mock導入方法

C言語で自作したライブラリのユニットテストを行うべく、Google Testの導入を試みました。
Google TestはC/C++のテストフレームワークです。

Google TestはVisual Studio 2019のプロジェクトテンプレートとして既に存在しているので
Google Testのヘッダーファイル（gtest.h等）を使用することは容易なのですが、
Google Mockの導入で手こずったので解決方法を紹介します。

# 読み進まる前に
Google Test / Google Mockの導入方法としては
ソースコードをGitHubからダウンロードしてビルドして～～等のやり方もありますが、
今回の紹介方法は__Visual StudioのNuGetを使用してGoogle Mockを導入__する方法です。
Visual Studio上のみで完結させます。

# 導入環境
* OS
    - OS Name : Microsoft Windows 10 Pro
    - Version : 10.0.18363 Build 18363
* Microsoft Visual Studio 2019
    -  Version : 16.4.3
    -  Installed Version : Community
    -  NuGet Package Manager : 5.4.0

# Google Test
前述したほうにGoogle Testは、Visual Studio 2019のプロジェクトテンプレートとして既に存在しているので
導入は簡単です。
プロジェクト作成からGoogle Testを選択して下さい。
細かい設定は今回割愛します。

# Google Mock

## インストール
NuGetからGoogle Mockパッケージをインストールしました。

Google Mockで検索するといくつかヒットします。
今回は、`googlemock.v140.windesktop.static.rt-dyn`の`1.7.0.1`をインストールしました。

インストール後、プロジェクトのコンフィグファイルは

```config
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="googlemock" version="1.10.0" targetFramework="native" />
  <package id="Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn" version="1.8.1.3" targetFramework="native" />
</packages>
```

のようになるかと思います。
`googlemock`が追加されているのが確認できます。

## ファイル読み込み
インストール完了したので、Google Mockを使用するために`gmock/gmock.h`をインクルードしても場所が見つからないというエラーになりました。

試しにインストールしたGoogle Mockパッケージの場所（`プロジェクトの場所\packages\googlemock.v140.windesktop.static.rt-dyn.1.7.0.1`）を見ると
`include`下のディレクトリ名が`gtest`となっていました。

__`gmock`じゃないの？？？？__

ってことでディレクトリ名を`gtest`から`gmock`に変更することで
無事にインクルード出来るようになりました(^ω^)
[Android Studio]Code Irisを入れたらプロジェクトが起動しなくなった

# 問題
- Android StudioにCode IrisというPluginを入れたらプロジェクトが起動しなくなった

# 解決手順
1. Android Studioを閉じる
1. pluginsフォルダの、android-hogeのような名前以外のファイルを別の場所に移動
1. Android Studioを再起動
1. 「Code Irisにより問題が発生しました」というようなメッセージが表示されるので、disableにする
1. 2で移動したファイルをpluginsフォルダに戻す
1. Android Studioを再起動

# 参考
- https://inducesmile.com/android/android-studio-fatal-error-initializing-plugin-code-iris/
CFn で作ったec2にyum update 出来ない

原因
VPC作成の時
`EnableDnsSupport`　`EnableDnsHostnames` を適用していなかったため

訂正前

```
MemosVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: false
      EnableDnsHostnames: false
      Tags:
      - Key: "Name"
        Value: "MemosVpc"
```

訂正後

```
MemosVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
      - Key: "Name"
        Value: "MemosVpc"
```
品質担保に本気で取り組んでみている話

株式会社オズビジョンの @terra_yucco です。
2020/01 現在、オズビジョン 6 年目。内部ではちらほら配置換えもありつつも、なんだかんだでこの 6 年の大半はメインプロダクト「ハピタス」を見ています。

# 前置き
現在、品質向上のために仕様統括＋品質管理というロールを担っております。
主な役割は、着手が決まった案件について、以下の項目のそれぞれについてレビューの必要性があるかを判断し、必要となったものはレビューをすること。もちろん担当チームは自分たちでも確認をしますが、仕様統括という形で全体のつながりも意識しながら追加で確認しています。

- 要件定義
- テスト設計
- リリース・反映手順、動作確認手順
- 後日確認手順
- 切り戻し手順

ansible のコードや、aws 関連のスクリプトの場合はコードを見たりもしますが、基本はプロダクトのコードは見ません。

# なぜこんなロールがあるのか
一時期、リリース前後に不具合が頻発することがあったため。また、検出した不具合は、単純なテスト忘れや確認漏れが多く、誰かがチェックしていれば防げたんじゃないかと思われるようなものも多かったため。

# どんな点に気を付けて見ているのか
## 仕様統括として
### 要件定義

- これまでの機能との相反・不整合がないか
    - ユーザステータスの遷移
    - 既存データとのドメインチェック

### テスト設計

- 動作テストにおいて、提供機能のハッピーパスがテストされているか
- 機能的に見落としている、重要な組み合わせパターンはないか
- 事業的に意味を持つ、入力値のバリエーションはないか、ある場合はテストパターンに含まれているか

### リリース・反映手順、動作確認手順

- 複数機能をリリースする場合、前後関係は正しく保たれているか
- 機能にダウンタイムが発生する場合、調整はされているか、メンテナンスが必要な場合は調整済か
- 動作確認手順は、実際にリリースする変更部分の動作を確認できるものとなっているか

### 全般

- 過去に出た不具合と類似の事象が発生しそうな場所はないか

## 品質管理として

若手のメンバーも多いので、かなり一般的な見方をしています。これは主にテスト設計の確認に適用しています。
本来は DB 設計やセキュリティ観点なども要件定義で見たほうが良いのですが、これは現状では各チームに移管しています。

### 基本

- 誰が見ても実施する内容がわかるテスト設計となっているか
- 期待するインプットが明確か、全て洗い出されているか
- 期待する想定の結果が明示されているか　※「正常であること」などは想定結果ではない

### 単体観点

- 各ドメインの値、境界値
- カバレッジ

### 結合観点

- 状態遷移を伴う場合には、実際に遷移させるテストを行う設計になっているか
- 既存の機能に影響する場合、その機能全体の通しのテストも行う設計になっているか

### 全体

- 必要なテストが、コードレベルの UT などとマニュアルテストを合わせて網羅されているか

# 効果

普段の業務にプラスオンして何名かのメンバーで分担していますが、レビューを間に挟んで別の人の目を入れたということはいい方に働いており、このレビューをしっかりやった案件ほど不具合が出ないという結果にはなっています。
ただし当たり前ながら QCD の CD を少し犠牲にして Q を上げている結果にはなっているので、次はここに何らかの手を打っていきたいと考えています。

# これから

既に一回実施していますが、このレビューを通して得られた知見を社内にシェアし、将来的には特別なレビューを挟まなくても各チームで品質向上に取り組んでいければと考えています。
皆さんのレビュー観点などももしあればぜひ教えてください。