OmniAICreator/Qiita-1.07M huggingface dataset:
アルゴリズム力UP!『ARC 005 C 器物損壊！高橋君』を解いてみよう

# 記事を読むとわかること
- 記事の作者の『ARC 005 C 器物損壊！高橋君 』の解法がわかる
- 記事の作者のアルゴリズム問題へのアプローチがわかる

# 今回の問題 

**ARC 005 C 器物損壊！高橋君**　→ [問題文はこちら](https://atcoder.jp/contests/arc005/tasks/arc005_3)

# 記事の作者の回答のコード
```python
from collections import deque

# ==============================================================
# return: スタートとゴールなどを入力すると、高橋くんがたどり着けるかどうかを出力
# pre, post: 問題文に記載 -> 2 3 のマスの解釈を 3行目の2列目にあるマス というのではなく、2次元配列の2行目の3行目にあるマス　と最初考えた方が混乱しないかも。

# ==============================================================
# ★ step1 問題文を解くために必要な迷路をインプットする
# ★ step2 深さ優先探索でスタートからゴールにむかい上下左右に探索をさせる。(今回は塀を2回まで叩ける関係で幅優先だとTLEになる)
# ★ step3 塀を破壊した回数を記憶させ、壁を破壊するたびにインクリメントさせる
# ★ step4 探索で進む先のマスで0~2回で壁を破壊し進んだかを記憶させ、3以上の時に弾くようにさせる
# ★ step5 もしゴールにたどり着いた/着かなかった場合にYES/NOを返す

#問題文に則り迷路大きさをインプット
h, w = map(int, input().split())
#問題文に則り迷路をインプット
maze = [list(input()) for i in range(h)]

def dfs():
    
    #上下左右の4方向に分岐させる (今回は1行目の0行目みたいな解釈になるので、どの方向かは厳格に考えない方が無難？)
    dx = [1, 0, -1, 0]
    dy = [0, 1, 0, -1]
    
    #あるマスを探索したかを壊した回数ごとに持つために、マスごとで[False, False, False]を持つ
    d = [[[False] * 3 for j in range(w)] for i in range(h)]
    
    
    #スタートとゴールのインデックスを探索し、それぞれ代入する
    for i in range(h):
        for j in range(w):
            if maze[i][j] == "s":
                sx = i
                sy = j
            if maze[i][j] == "g":
                gx = i
                gy = j
                
    #スタックの作成(今回は深さ優先のため)
    que = deque([])
    #スタートをスタックにいれる(3つ目は塀を壊した回数)
    que.append((sx, sy, 0))
    #スタートマスに探索した記憶をつける
    d[sx][sy][0] = True

    while que:
        #popメソッドを使い後に入れたデータを先出し、深さ優先を表現 (イメージ、究極直線を進みまくる)
        p = que.pop()
        #ゴールか否かを判定
        if p[0] == gx and p[1] == gy:
            break
        #4方向を指定
        for i in range(4):
            nx = p[0] + dx[i]
            ny = p[1] + dy[i]
            #新しいマスに向かうに当たり、前回時点での破壊した回数を代入
            t = p[2]
            #迷路範囲外かどうかを判定
            if not (0 <= nx < h and 0 <= ny < w):
                continue
            # 次が壁でかつすでに2回壊してる状態がどうか
            if  maze[nx][ny] == "#" and t == 2:
                continue
            #既に探索済みかどうか
            if d[nx][ny][t]:
                continue
            # 塀なら壊して進む(上のifで既に回数のハンドリングは済んでいる)
            if maze[nx][ny] == "#":
                #塀の破壊回数をインクリメントしつつ新しいマスに記録
                que.append((nx, ny, t + 1))
                d[nx][ny][t + 1] = True
            else:
                #塀の破壊回数をそのままにしつつ新しいマスに記録
                que.append((nx, ny, t))
                d[nx][ny][t] = True
                
    #ゴールの座標を壊した回数でループし、いずれかにTrueがあればTrueを返す
    for i in range(3):
        if d[gx][gy][i]:
            return True

#ゴールにたどり着いたかどうか
if dfs():
    print("YES")
else:
    print("NO")


```
# 回答の解説

## 記事の作者が思う今回のアルゴリズム問題の回答ポイント

- 迷路やスタート及びゴールを入力する際、迷路が配列で扱う関係で０番目スタートであることを意識
- 深さ優先探索であるため、後入先出しのためのpopメソッドを使う
- 条件分岐として、迷路の範囲内か、塀でありかつ破壊回数が2回か、未探索先か壁か、を判断する
- 探索先に分岐に合わせてスタックに破壊回数を記録し進める

#このコードの課題感

- 今回の前提として〜行目の〜行目のマスとしたが、仮に「2行２列目」がスタートであると解釈する場合、今回の変数x(列),y(行)がちょっと気持ちが悪くなる
大数の法則ためしてみた

統計学の勉強をしていて、「大数の法則」が出てきました。法則の内容的には、「まぁ多分そうなんだろうね」って感じだったんですが、一応シミュレーションしてみるかってことで、やったことをまとめてみようと思います。

# 大数の法則とは？
互いに独立な確率変数$X_1,X_2,...,X_n$が母平均$\mu$であるとき、標本平均$\frac{X_1+X_2+...+X_n}{n}$はnが大きくなるほど母平均に近づくいうもの。

```math
\lim_{n \to \infty}\frac{X_1+X_2+...+X_n}{n} \rightarrow \mu
```

この式は数学的に厳密ではないのかもしれないですが、イメージはこれで大丈夫だと思います。直感的には理解できるのですが、実世界で大量に試行する（nを増やす）というのもなかなか根気がいりますよね？ということで、サイコロを例にして「大数の法則」が本当かどうかシミュレーションしていきます。

# サイコロシミュレーション
１～６までが同じ確率（$\frac{1}{6}$）で出るサイコロで、サイコロを振る回数を1~10000回まで増やしていき、試行回数が標本平均に与える影響をグラフで確認できるようにします。
## シミュレーションコード
```python
import matplotlib.pyplot as plt
%matplotlib inline
import random
from collections import Counter 

# 試行回数リスト
numTrials = list(range(1,10001))
meanlist = []

for numTrial in numTrials:
    resultData = [ random.randrange(1,7) for _ in range(numTrial)]
    countResult = Counter(resultData)
    sumCountResult = 0
    for num in (countResult.keys()):
        sumCountResult += num * countResult[num]
    mean = sumCountResult / numTrial
    meanlist.append(mean)

plt.plot(numTrials,meanlist)
plt.xlabel("Trials")
plt.ylabel("mean")
plt.axhline(3.5, ls="--", color="magenta")
plt.savefig("verification_lawOfLargeNumber.png")
plt.show()
```

## シミュレーション結果
以下がシミュレーション結果です。横軸はサイコロを振った回数、縦軸はサイコロをn回振ったときの出た目の平均値です。
![verification_lawOfLargeNumber.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541501/e6fe60df-2a71-f372-4570-b0ecfca9a481.png)

ピンクの点線が、平均値3.5のラインで回数を重ねるごとに収束していることが分かります。「大数の法則」ってやっぱり正しそうですね。まぁ当たり前か・・・(笑)

あと、今回サイコロの出目を乱数を使ってシミュレーションをしていますが、シートを固定していないので、同じプログラムを実行しても、同じグラフを書くことはできませんので、ご了承ください。ただし、平均値が3.5に収束するという点は何度実行しても変わりません

# 最後に
今回はただの確認だったので、特に面白い話ではなかったですね。もっと面白いお話ができるように勉強頑張ります。
Rails 後からNot null 制約を付与する

DBのカラムにて、Not null制約をつけるのを忘れていたことに気がついた
もとのマイグレーションファイルを修正するのは、よくないとのことなので
また新たにNot null制約を付与するマイグレーションファイルを作成する

```
rails g migration ChangeCloumnsNotnullAddUsers
```
マイグレーションファイルを作成

方法は2種類あるらしい！

##change_columnメソッド

```rb:db/migrate/***_change_columns_add_notnull_on_users.rb
class ChangeColumnsAddNotnullOnUsers < ActiveRecord::Migration[5.2]
  def change
    change_column :users, :name, :string, null: false
    change_column :users, :email, :string, null: false
    change_column :users, :password, :string, null: false
  end
end
```

`false` → NotNull制約を付与`する`
`true` → NotNull制約を付与`しない`

```
change_column :テーブル名, :カラム名, :データ型, null: false or true
```

change_columnメソッドを使うのが一般的だったりするのかな…！


##change_column_null

```rb:db/migrate/***_change_columns_add_notnull_on_users.rb
class ChangeColumnsAddNotnullOnUsers < ActiveRecord::Migration[5.2]
  def change
    change_column_null :users, :name, false
    change_column_null :users, :email, false
    change_column_null :users, :password, :string, false
  end
end
```

`false` → NotNull制約を付与`する`
`true` → NotNull制約を付与`しない`

```
change_column_null :テーブル名, :カラム名, false or true
```

こっちは、記述量が少なくて直感的な気がする！

`change_column_default`メソッドもあるらしいが、追々調べていこう

##参考記事
[Railsガイド](https://railsguides.jp/active_record_migrations.html)
https://qiita.com/MOssan-32/items/89afc9e4375215f8b5d2
https://qiita.com/akinov/items/852fe789fe98a44350a9
FlutterアプリにCrashlyticsを導入する

FlutterアプリにCrashlyticsを導入する。

## 前提

- FlutterアプリのFirebaseプロジェクトを作成済み
- Firebaseプロジェクト設定から、AndroidアプリとiOSアプリを登録して、GoogleService-Info.plistを取得済み

## ビルド設定

### 基本

https://firebase.flutter.dev/docs/crashlytics/overview/

### FlavorでFirebaseのプロジェクトを分ける場合

"develop"と"production"の2つのFlavorでFirebaseのプロジェクトを分けているとする。
flavor導入の設定は済んでいる前提。

#### iOSの設定手順

全てXCode上の操作。

1. ios/Runner.xcodeworkspaceをXCodeで開く
2. GoogleService-Info.plistの設定(Runnerフォルダ配下)
  - develop向けにGoogleService-Info-develop.plistを追加
  - production向けにGoogleService-Info-dev.plistを追加
  - GoogleService-Info-dev.plistを追加
     - **Target MembershipにRunnerを含めること**
     - 中身は適当でも大丈夫のはず
3. XCodeのトップ階層にあるRunnerを開く(フォルダでなく、.xcodeprojの方)
4. Build Phasesを開く
5. 画面上の方の＋ボタンからNew Run Script Phaseを選択
6. Build Phansesの最後に「Run Script」が追加されるので、上の方にある「Check Pods Manifest.lock」の直後に移動する
  - 名前も分かりやすい名前に変えておくと良い
  - 「Replace GoogleService-Info.plist」など
7. flavor別のGoogleService-Info.plistを所定の場所と名前でコピーする
  - 追加したRun Script Phaseに、以下のスクリプトを書く。

```bash
# $CONFIGURATIONは、Build Configurationの名前が入る。
# Build Configurationにflavor名を含めていることが前提の判別方法。

if [[ $CONFIGURATION == *"develop"* ]]; then
    cp $SRCROOT/Runner/GoogleService-Info-develop.plist $SRCROOT/Runner/GoogleService-Info.plist
else
    cp $SRCROOT/Runner/GoogleService-Info-production.plist $SRCROOT/Runner/GoogleService-Info.plist
fi
```

8\. 追加したRun Script PhaseのOutput Filesに、「$SRCROOT/Runner/GoogleService-Info.plist」を追加する

9\. \${PODS_ROOT}/FirebaseCrashlytics/runを実行しているPhaseのInput Filesに「\$SRCROOT/Runner/GoogleService-Info.plist」を追加する(基本の手順に従って、\$(BUILT_PRODUCTS_DIR)/\$(INFOPLIST_PATH)を追加している場合は、そちらは削除してOK)

#### Android

- android/app/developに、develop向けGoogleService-Info.plistを置く
- android/app/productionに、production向けGoogleService-Info.plistを置く

(ファイル名にflavor名は含める必要なし)

## Dart

Crashlyticsを使うための準備処理をmain()あたりに書く。

```dart:main.dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();　  # これをしておかないと、Firebase.initializeApp（）がエラーになる
  await Firebase.initializeApp();

  // DartのエラーをCrashlyticsに報告する
  FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;

  // Dart以外のエラーログをCrashlyticsに報告する
  Isolate.current.addErrorListener(RawReceivePort((pair) async {
    final List<dynamic> errorAndStacktrace = pair;
    await FirebaseCrashlytics.instance.recordError(
      errorAndStacktrace.first,
      errorAndStacktrace.last,
    );
  }).sendPort);

  runApp(App());
}
```

後は、クラッシュさせて見て試す。以下の処理でクラッシュさせられる。

```dart
FirebaseCrashlytics.instance.crash();
```
git configの設定がおかしくなったときにやったことを纏める

## はじめに
個人的備忘録として発信しています。

## 起きたこと
リモートリポジトリにpush出来なくなりました。
エラーを見ると、何やらリモートリポジトリのurlが見つからないとかなんとか

## 本編
リモートリポジトリの設定を確認

```
$ git remote -v
origin  https://github.com/XXXX/YYYY.git (fetch)
origin  https://github.com/XXXX_master/YYYY.git (push)
```

何故かpushとfetchで異なるリモートリポジトリになっていた。
fetchの方のリモートリポジトリにpushしたい。

そこでgit configで設定を確認

```
$ git config -l --local
・・・
remote.origin.url=https://github.com/XXXX_master/YYYY.git
・・・
```

-l : git configの設定一覧を見れる
--local : リポジトリ単位の設定
※ --system : システム全体(全ユーザー共通の設定)
　  -- global : ユーザー単位の設定

これを見ると、リモートリポジトリの設定が間違っていそう
なのでgit configの編集をします

```
$ git config --local --edit
```

と入力すると下のような画面が出ます

```
[core]
	repositoryformatversion = 0
	filemode = false
	bare = false
	logallrefupdates = true
	symlinks = false
	ignorecase = true
[remote "origin"]
	url = https://github.com/XXXX_master/YYYY.git
	fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
	remote = origin
	merge = refs/heads/master
```

今回はリモートリポジトリの設定を変えたいので

```
url = https://github.com/XXXX_master/YYYY.git
↓
url = https://github.com/XXXX/YYYY.git
```

のように本来pushしたいリモートリポジトリ先に変更します
これで無事push出来ました

## あとがき
この設定ですが、自分が初めてセットアップしたPCだったので、色々おかしかったのかもしれません
heroku コマンドを使用しようとした時のエラー

##エラー発生状況
デプロイ後、アプリを開くとエラーが出たため　herokuのログを見るためコマンドを打つと以下のエラーが発生した

##エラー内容
```
$ heroku logs
 ›   Warning: heroku update available from 7.45.0 to 7.47.4.
 ›   Error: Missing required flag:
 ›     -a, --app APP  app to run command against
 ›   See more help with —help
```

##解決策
エラー文の

```
Warning: heroku update available from 7.45.0 to 7.47.4.
 ›   Error: Missing required flag:
```
で検索を行い以下の参考記事から
https://reasonable-code.com/heroku-update/

```
 Warning: heroku update available from X.X.X to Y.Y.Y 
```
とエラーが出た時は

```
Heroku update
```
　を打つことで解決できる

だが上記でも解決できない場合がある

```
$ heroku update
 ›   Warning: update with: "npm update -g heroku"
heroku: Updating CLI... not updatable
Updating completions… done
```

これが出た場合はnpm update -g herokuを実行すると解決できる

```
 $ npm update -g heroku
/usr/local/opt/nvm/versions/node/v14.3.0/bin/heroku -> /usr/local/opt/nvm/versions/node/v14.3.0/lib/node_modules/heroku/bin/run
+ heroku@7.47.4
added 3 packages from 2 contributors, removed 13 packages and updated 60 packages in 32.884s
$ heroku version
heroku/7.47.4 darwin-x64 node-v14.3.0
```
これでheroku コマンドが使えるようになる
Heroku logsを打つとエラーが出た時の対処

##エラー発生状況
デプロイ後、コンソールでHeroku logsを打つとエラーが発生した
##エラー内容
コンソール

```
Error: Missing required flag
```

##解決策
Herokuのサーバーにリポジトリを紐付けていないためエラーが表示されるため紐付けを行う
コンソールで以下を実施

```
heroku login
git remote -v
heroku git:remote -a <HerokuAppName>
git remote -v
```
ShaderToyのCubeMapAへの道、あるいは動的CubeMap入門


###ShaderToyのCubeMapAへの道、あるいは動的CubeMap入門
ShadertToyのスペシャルフューチャーとして、***CubeMapA***がある。
CubeMapAはhttps://shadertoyunofficial.wordpress.com/2016/07/20/special-shadertoy-features/
に解説があり、6枚のマッピング用のテクスチャーを用意しておく通常のCubeMapと違い、
シェーダーで動的にCubeMap用の画像を生成する機能である。以前のポストで、opennframeworksのアドオンであるofxshadertoyでCubeMapを実現する方法を実験した、
***「ShaderToy+openframeworksでキューブマップ」***
https://qiita.com/quittardis/items/9e1abab3babfeb3e1206
を参考にしていただきたい、

本稿では、まだ「ShaderToyのCubeMapAへの道」、つまり途上でなので最後までたどり着けるかどうかわからないが、CubeMapAを実現すべく試行錯誤を続けたい。
　ofxshadertoyの開発者のtiagosor氏が、そうこうするうちにCubeMapAをインプリメントしてくれるかもしれないが、そうなっても、Shadertoyやopenframeworksを使わない一般的なOpenGLの動的キューブマップの勉強にもなればと考えている。

　本稿は、上に述べた「ShaderToy+openframeworksでキューブマップ」をベースに進める。
###OpenGLで動的マッピンテクスチャーマッピング
要するに、キューブマップ用の6枚テクスチャーをFramebufferに紐づけられたメモリに上書きできればいいわけで、このために、OpenGLのフレームバッファを準備し、キューブマップ用に用意されたテクスチャーとフレームバッファを紐づけて、テクスチャー直接描き変えるということが目標である。
###Framebuffer
ヘッダーに、

```
GLuint FbCubeA;
GLuint TxCubeA;
```
とし、初期化するところに、

```
// CubeA用フレームバッファの準備
	FbCubeA = 0;
	glGenFramebuffers(1, &FbCubeA);
	glBindFramebuffer(GL_FRAMEBUFFER, FbCubeA);
```

とする。
次以下のようににテクスチャーをアサインし、6枚のグレーのテクスチャーで
初期化する。

```
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

int width, height;
	width = 512;
	height = 512;
	const int size = width * height * 3;  //   4 channels, unit8 
	GLubyte* data = new GLubyte[size];
	for (int i = 0; i < size; i+= 3) {
		data[i] = 200;
		data[i+1] = 200;
		data[i+2] = 200;
	}
```

###キューブマップ用の6枚の画像の登録

次にこの6枚をキューブマップ用のテクスチャーとして登録する。


```
	for (unsigned int i = 0; i < 6; i++)
	{
	
		glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
		
	}
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

	return textureID;

```

###フレームバッファのカラーバッファとしてテクスチャを設定
　このtextureIDをvoid ofApp::update()で以下のように利用する。
描画をスクリーンでなくテクスチャーの直接描くオフスクリーンレンダリングである。

  xmove はintとしてglobalで定義され、描画色をフレームごとに変化させて、
　TxCubeAは、グローバル変数で上で求めた、textureIDを代入する。

```
glBindFramebuffer(GL_FRAMEBUFFER, FbCubeA);
for (int face = 0; face < 6; face++)
{
    // フレームバッファのカラーバッファとしてテクスチャを設定
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, TxCubeA, 0);
    ofSetColor(255-(xmove%255), 0, 0);
　　xmove++;
    ofDrawRectangle(10, 10, 512, 512);
								
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
```

 これで、キューブマップの画像を動的に変化させられることがわかった。
今回は描かれる画像が上下左右に切れ目なくつながった正式のキューブマップ用のテクスチャー
ではないので、これをシェーダーで描けるようになるのが次の目標である。
（Framebufferのほかにもデプスバッファを用意する場合もあるが、今回は3Dのオブジェクトを描画しないのでデプスバッファは不要）
【読書記録】Kotlinイン・アクション -第9章-


書籍「Kotlinイン・アクション」を読んで個人的に疑問に思った点や調べたことについて記載する読書記録です。
書籍の内容については記載しません。

# 第９章の内容

第9章　ジェネリクス
https://book.mynavi.jp/ec/products/detail/id=78137

## ジェネリクスの注意点

- ジェネリッククラスの型パラメータTは通常Any?型でnull許容型であることに注意
- null非許容型にするためには上限限界を指定する必要がある

```kotlin
class Hoge<T : Any>{
    ・・・
}
```

## 不変

不変の説明が理解しづらかったので、整理。

```
2つの異なる型A,Bについて、MutableList<A>がMutableList<B>のサブタイプ、スーパータイプではない場合にMutableListは型パラメータについて不変である。
```

`MutableList<Int>はMutableList<Any>としては使えない`ことを不変という。

IntはAnyのサブタイプだが、MutableList<Int>はMutableList<Any>のサブタイプではないというのがポイント。

不変の逆が共変。

List<Int>はList<Any>として使えるため共変。

## ジェネリクスを共変にする

ジェネリクスの中で型引数のスーパータイプ、サブタイプの関係を維持するには`out`キーワードを明示的に指定して共変にする必要がある。

## outキーワード

`out`キーワードを指定した型パラメータTは戻り値としてしか使えないよう制限できる。これによって、共変の安全性を保証することができる。

## 反変

型パラメータのサブタイプの関係と逆になることを反変という。

Hoge<Number>がHoge<Int>のサブタイプの場合、IntはNumberのサブタイプであるという関係と逆になっているため、反変という。

`in`キーワードを使った場合には反変する。
Ubuntuを2つデュアルブートした後に片方のUbuntuをインストールしたSSDを抜いたら起動しなくなった時の解決法

# 起こった問題
僕は自作パソコンにUbuntuを入れて使っています。
普段は20.04をインストールして使っていて、ROSをやろうと思いWebサイトを調べてみたところ、18.04を使っているものが主流だったので、20.04をインストールしているM.2 SSD(Nvme)とは別のSATA SSDにUbuntu 18.04をインストールしました。  
この時、本当は20.04をインストールしていたM.2 SSDを抜いた状態で18.04を別のSSDにインストールしたかったのですが、M.2 SSDは手軽に抜き差しできないため、諦めました。
そこで20.04をインストールしたM.2 SSDを差したまま、18.04用にフォーマットしたSATA SSDを差してインストールを開始しました。この時、Live USBを使ってインストールし、インストール時にはSATA SSDをフォーマットしてインストールするオプションを選びました。そのほかはアップデートのダウンロード、ドライバの自動インストールもオンにし、それ以外はデフォルトのままだったと思います。  
その後、パソコンを起動するとUbuntu 18.04とUbuntu 20.04のどちらを起動するか選択する画面が出てきて、どちらを選んでも選んだOSが正常に起動しました。  
そして、20.04でも18.04と同様に正常にROSが起動することが分かったので、Ubuntu 18.04が不要になったので、SATA SSDを抜き、Ubuntu 20.04を起動しようとしたのですが、「GNU GRUB version 2.02 Minimal BASH-like ...」（一部省略)とだけ表示される画面が出てきて、**Ubuntu 20.04が起動できませんでした。**この画面からUbuntu 20.04を起動しようとしたのですが、うまく行きませんでした。
# 対処方法
調べてみるとこのような現象によく使われる対処法が、`Boot Repair`というソフトを使って壊れたgrubを修正する、という方法のようです。  
そこで[こちらのWebサイト](https://itlogs.net/ubuntu-grub-boot-repair/)(https://itlogs.net/ubuntu-grub-boot-repair/ )の様に`Boot Repair`をインストールし、推奨設定で、**一度Ubuntu 20.04と18.04の両方のSSDを差した状態で**`Boot Repair`を実行しました。  
その後、Ubuntu 18.04のSATA SSDを抜いた後、もう一度起動するとUbuntu 18.04のSSDはないはずなのに、Ubuntu 20.04とUbuntu 18.04を選択する画面が表示されました。  
その後、Ubuntu 20.04を選択して起動し、もう一度`Boot Repair`を実行したところ、再起動しても元通り、自動でUbuntu 20.04が起動する様になりました。
# 考えられる原因
そもそも原因としてデュアルブートが関係ない、という可能性もありますがUbuntu 18.04のSSDを抜くか挿すかで挙動が変わるので、デュアルブートしたタイミングでgrubの設定に何かしらの変化（おそらくどちらのOSを起動するか選択するための設定か何かが変更された)が起き、Ubuntu 18.04のSSDを抜いてもその変化が戻らなかったために齟齬が起きたのではないかと思います。
# 感想
デュアルブートは危険、と言われる理由がわかる様な気がしました。パーティションで分けているわけでもないのでお互いに干渉することはないだろうと思っていましたが、そんなことはないようです。
皆さんもデュアルブートには気をつけてください。
Rackでmultipartをparseするmiddleware

## Rack::Multipart.parse_multipart env


https://www.rubydoc.info/gems/rack/Rack%2FMultipart.parse_multipart

```config.ru

module Middleware
  class MultipartParser
    def initialize(app)
      @app = app
    end

    def call(env)
      if env["CONTENT_TYPE"] =~ /multipart\/form-data/
        params = Rack::Multipart.parse_multipart(env).to_h

        env["multipart_params"] = params
      end

      @app.call(env)
    end
  end
end

use Middleware::MultipartParser
run -> env { [200, {}, [env["multipart_params"].to_s] ] }
```
【Rails】ログイン結果をJSON形式で返す(初学者用)

## はじめに
API通信って最初何やってるのかよくわかりませんでした。
今回は、同じ悩みを持つ人が少しでもイメージが持てるように、
UIはつくらずに、実行後のレスポンス結果をJSONで返す実装を紹介します。
題材はRails初学者が恐らく最初に学んだであろうサインアップ、ログイン。

## コード
※ userテーブルのカラムはemail,password
※ アクセスURLはhttp://localhost:3000/api/v1/*** とします。localhost:3000のままでもok

```ruby:routes.rb
Rails.application.routes.draw do
  # namespaceで /api/v1/の階層をつくる
  namespace :api, format: :json do
    namespace :v1 do
      post :sign_up,  to: 'users#sign_up'
      post :login,   to: 'users#login'
    end
  end
end
```

```ruby:users_controller.rb
class Api::V1::UsersController < ApplicationController ## クラス名を階層に合わせる
  def sign_up
    user = User.new(user_params)
    if user.save
      render status: "200", json: { result: user }  # 保存成功時にユーザー情報をjson形式で返す
    else
      render status: "400", json: { result: "メールアドレスまたはパスワードが有効ではありません" }
    end
  end

  def login
    user = User.find_by(email: params[:email])
    if user && user.authenticate(params[:password])
      render status: "200", json: { result: user }  # ログイン成功時にユーザー情報をjson形式で返す
    else
      render status: "400", json: { result: "メールアドレスまたはパスワードが一致しません" }
    end
  end

  private

    def user_params
      params.permit(:email, :password)
    end
end
```

## Postmanで試してみる
### 使ったことない人のために
インストール（https://www.postman.com/） したら画像の通りやってみてください。
設定できたら「Send」ボタンで実行！
![スクリーンショット 2020-12-20 21.45.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670405/fa640959-fe70-3c4c-d556-5dde801ebbf7.png)
![スクリーンショット 2020-12-20 21.47.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670405/75519a59-2ca5-fd46-e6f1-fb9ed24aef9f.png)
### サインアップ成功例
![スクリーンショット 2020-12-20 21.34.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670405/46108849-1e01-0ca6-3114-c9fd464e1b95.png)
### ログイン成功例
![スクリーンショット 2020-12-20 21.35.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670405/a524676a-2612-100d-c5f5-151df495615f.png)
### ログイン失敗例
![スクリーンショット 2020-12-20 21.36.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670405/2c61c5e9-04b7-4124-3453-e0ea2930587b.png)

## レスポンス内容を全部表示したくないとき
as_jsonを使うと簡単に制限できます。
参考URL　https://qiita.com/tbaba/items/ea44025b056f708ab0b4

## おわりに
知ってしまえば意外と簡単でした。でも初学者はこれを知るのに時間がかかるんです。。。
誰かの助けになれば幸いです。
RSpecについて（Rails）

## RSpecとは？
　Ruby言語用のテストフレームワークのこと。RSpecにテストコードを記述してターミナルでテストコードを実行することで、自動的にテストコードに沿ったバグチェックができるツール。
## 導入方法
#### ①RSpecのGemを導入する。

```ruby:Gemfile
group :development, :test do
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
#　↓↓今回追加する記述↓↓
  gem 'rspec-rails', '~> 4.0.0' 
end
```

```ruby:ターミナル
% bundle install
```

#### ②RSpecの初期ファイルを作成する。

```ruby:ターミナル
% rails generate rspec:install
```
このコマンドを実行することで下記のファイルが作成される。

```ruby:ターミナル
.rspec
spec
spec/spec_helper.rb
spec/rails_helper.rb
```
JavaScript: setTimeoutは初心者泣かせ？よくある失敗例をまとめてみた

#はじめに
JavaScriptの`setTimeout`関数は、記事によっては「タイマー処理」だとか「何秒か遅らせて実行したい時に利用する」と紹介されていることがあるが、これは正確な説明ではないように思える。

（私のような）初心者は、そもそもJavaScriptの非同期処理というものをよくわかっておらず、こういう説明をそのまま鵜呑みにして返って痛い目に遭って他の記事を探すということを繰り返してきた。結局JavaScriptの非同期処理についての正確な理解が最短の道なのだが・・・そのためには、[JavaScriptの非同期処理をできる限り正確に理解する](https://qiita.com/UTDoi/items/d49ea919818d9b519f93)がとてもわかりやすいので一読を勧める。

結論からいうと、

- `setTimeout`の第1引数はタスクキューに入れて後から実施する関数を記載する。
- `setTimeout`は第2引数に**タスクキューに入れられるまでの待機時間（単位: msec)**を指定する。
    - この時間を待機した後は、**いったんタスクキューに入れられて実行待ち状態になる**。
    - **実行待ち状態だからといって、すぐに実行されるとは限らない**。もしメインスレッドで他に実施する処理がなくなった段階で、ようやくタスクキューにたまっている処理がFirst-In First-Outで処理されていく。逆に言うと、もしメインスレッドでとても重い処理があれば、このタスクキューに格納された処理はその分待たされる。つまり、実際に実行されるまでの時間は、第2引数で指定した時間よりずっと長くなる可能性もある。


#第1引数に対するよくやる失敗。

- 関数を指定してしないといけないのに、関数を実行してしまっている。

```javascript:サンプル
function fn1(value) {
  console.log(value);
}
setTimeout(fn1(5), 5000); //5秒待たずに即時に実行されてしまう。
```

この場合、fn1(5)を実行することによって、(return文が書かれていないため）fn1(5)はundefinedを返す。つまり、`setTimeout(undefined, 5000)`が実行されたのと同じであり、実質機能していない。

第1引数に関数を指定してあげるために、

```javascript:回避策
function fn1(value) {
  console.log(value);
}
setTimeout(function(){fn1(5)}, 5000); 
```

のように関数定義をしてfn1(5)をラップしてもいいのだが、そもそもsetTimeoutに引数を渡すために第3引数が用意されているため、以下のように記述するのが普通である。

```javascript:一般的な書き方
function fn1(value) {
  console.log(value);
}
setTimeout(fn1, 5000, 5); 
```



#第2引数に対する誤解。

##例1: 待機時間0でも即時に実施されない

```javascript:B->Aの順で出力される。
setTimeout(function () {
  console.log('A');
}, 0);
console.log('B');
```

`0msec（待機しない）`を指定しても、必ず`A`は`B`の後に実行される。`A`を表示する処理はタスクキューにいったん入れられて、全ての処理が終わった後に初めてタスクキューの処理が実施されるからである。

##例2: setTimeoutの実行順ではなく、タスクキューへの登録順

```javascript:B->C->Aの順で出力される。
setTimeout(function () {
  console.log('A');
}, 2000);

setTimeout(function () {
  console.log('C');
}, 1000);

console.log('B');
```

`A`を出力するsetTimeoutが先に実行されたからといって、`A`が先に出力されるわけではない。`A`は2秒後にタスクキューに登録され、`C`は1秒後にタスクキューに登録されるため、タスクキューに登録されたのは`A`より`C`の方が先である。そのため、`A`より`C`が先に出力される。

##例3: タスクキューの処理がなかなか開始されないケース

```javascript:待機時間0秒だと思ったが、実際はBが表示された後にAが表示されるまで5秒間かかる（実際はタスクキューに入るまでが0秒だというだけ）
setTimeout(function () {
  console.log('A');
}, 0)
console.log('B');

const startTime = new Date();
while (new Date() - startTime < 5000); //5秒間ループを続ける
```

`A`は即時にタスクキューに登録され、次に`B`が出力される。
しかし、その後にずっとメインスレッドでの処理が行われている（5秒間のループ）ため、その間はタスクキューの処理は実施されない。メインスレッドでの処理が終わって初めて`A`が表示される。setTimeoutの待機時間が0msecであったとしても、`B`の前に`A`が表示されるわけでもなく、`B`が表示された直後に`A`がすぐ表示されるわけでもない。


##例4: varを変数に利用したケース

これは、setTimeoutの問題というより`var`/`let`の問題と言ったほうがいいかもしれないが、いちおう補足として書いておく。

```javascript:3,3,3と出力される。
for (i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i)
  }, 1000);
}
```

これは、変数iがグローバルスコープのため、`console.log(i)`の処理が3つキューに入れられて実際に表示しようとした段階で、ループが完了してi=3になってしまっているため、全部3と表示されてしまうという問題である。

```javascript:0,1,2と出力される。
for (let i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i)
  }, 1000);
}
```

このようにletで書いておけば、変数iはループ間で共有されないため、メインスレッドが終わっても他の値に変わっていることはない。そのため、期待通りの動きになる（[この記事](https://qiita.com/querykuma/items/43a7e97f29ba34866b9e#q1-%E7%AE%B1%E3%81%AE%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%82%92%E8%A9%B3%E3%81%97%E3%81%8F)も参照。）
機械学習の実装の勉強の記録④（KerasでMNISTデータの学習）

#はじめに
この記事は個人の学習の記録です。
内容は以下の参考記事で、丁寧に詳しく書かれています。
皆様は以下を参考にしてください。

[参考にした記事](
https://weblabo.oscasierra.net/python/keras-mnist-sample.html)
[ソースコード](
https://gist.github.com/oscasierra/cbca3195b8dfc7fdf6795e752cdc8f15)


##実施時のメモ
上記のコードをGoogle Colaboratoryで実施した。
Colaboratoryは補完してくれて便利だ。
以下、ソースコードと見並べて参照することを前提としたメモ。

###MNISTデータを読み込む
keras.datasetsからmnist.load_data()を使って、訓練データ（60000個）、テストデータ(10000個)を読み込む。
なお、読み込んだデータx_train及びx_testの形状は以下の値を実行（評価）すればわかる。

```
x_train.shape
x_test.shape
```

###MNISTデータを加工する
28×28次元のデータを784次元のベクトルにrehsapeし、データの方をfloat32に修正する。
さらに、各値を255で割っている。
これは多分画像データの各ドットが0～255の値を取っているのを0～1の範囲に正規化する処理。
機械学習ではデータを標準化や正規化してから扱う。

[標準化と正規化]
(https://qiita.com/yShig/items/dbeb98598abcc98e1a57)

また、目的変数(y)がその大きさに意味をなさない変数であるため、目的変数の数値の大きさが結果に影響を与えないように、カテゴリカルデータ（one-hot表現）に変換。
※もちろんこれらの加工処理はtestデータに対しても行う。

```
x_train = x_train.reshape(60000,784).astype('float32')
x_train /= 255
y_train = keras.utils.to_categorical(y_train,10)
```
###モデルの構築
入力層と全結合層（=ここでは出力層）の２層からなるモデルを作っている。
入力層は784ノード。全結合層は結果を10分類したいので、ノードが10と指定されている。
また、出力層の活性化関数は教科書通りにsoftmaxを使用。
今回は誤差関数としてcategorical_crossentropyを使用。以下リンク先によると、カテゴリカルにした目的変数に対するクロスエントロピーを使用する場合はこれを使用するらしい。

- categorical_crossentropyについて
Keras Documentation:[損失関数の利用方法](https://keras.io/ja/losses/#categorical_crossentropy)
>categorical_crossentropyを使う場合，目的値はカテゴリカルにしなければいけません．



- クロスエントロピーとは？
[交差エントロピー](https://ja.wikipedia.org/wiki/%E4%BA%A4%E5%B7%AE%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AD%E3%83%94%E3%83%BC)


- rmspropとは？
勾配の大きさに応じて 学習率を調整した(ミニバッチ)SGD。
最適化アルゴリズムに関しては、下記の記事が非常に参考になった。


[【2020決定版】スーパーわかりやすい最適化アルゴリズム -損失関数からAdamとニュートン法-](https://qiita.com/omiita/items/1735c1d048fe5f611f80)

また、こちらの記事も詳しそうなので後で読みたい。
[Intro to optimization in deep learning: Momentum, RMSProp and Adam](https://blog.paperspace.com/intro-to-optimization-momentum-rmsprop-adam/)


###学習
epochs:エポックは１つのデータを何回繰り返して学習させるかの値
fit関数の引数については[こちら](https://keras.io/ja/models/sequential/)を参考にした。
Redmi note 9s custom romの焼きかた

## 参考にさせていただいた記事様
![【Xiaomi】Redmi note 9sにカスタムROM EvolutionX を焼く(2020最終版)](https://mitanyan98.hatenablog.com/entry/2020/12/24/234616)

## Miflash_Unlockを使用してアンロックする
現在162時間
## MiFlashを使用してGrobalimage11.07を焼く
1. FASTBOOTに入るためにシャットダウン後ボリュームマイナスボタンを長押しで起動する
2. refreshボタンをクリックしてデバイスの接続確認を行う
3. 下部のclean allを選択
4. 11.07を指定してflash

![](https://i.imgur.com/rSOJa1h.png)
## Twrpを入れる
1. `adb-setup-1.4.3.exe`でadb環境を構築
2. Power Shellで`fastboot devices`を実行してデバイスの接続確認を行う
3. `fastboot flash recovery .\twrp_curtana.img`を実行
4. `fastboot reboot`実行と同時にボリューム上ボタンを長押し

## custom romを焼く
1. 好きな場所に焼きたいromを転送
2. Twrp起動後Wipe → Advanced Wipe → Dataのみにチェック → Swipe to Wipe
3. ホームに戻りInstall → 好きなromを選択
5. Swipe to confirm Flash → Wipe Cache/Dalvik → Swipe to Wipe → Reboot System

## Twrpに戻ってきた場合
1. Wipe → Format Data → yes → enter → Reboot System
Git hook 活用してみる

# 前提
- いま活動してるプロジェクトのひとつでは、GAE上で複数のアプリケーションを動かしている
- デプロイ自動化のしくみがあって、特定のルールに沿ったタグをpushするとGitHub Actionsやらでステージング環境/本番環境に各サービスの最新バージョンがデプロイされる（トラフィック移行は手でやる）
- 全サービスがデプロイされるのはまあまあ時間がかかる

# 悲劇
ある日チームメンバーがステージング環境を全更新しようとしてタグ打って昼メシして、戻ってきたらGAEの制限（プロジェクト内には全GAEサービス合算で210個のバージョンしか存在できない）に引っかかってしまってデプロイできてなくて、泣いてた

# 対策を考える
いまのとこ何バージョン残しとくとかのかっちりしたルールとかがなくて多くなってたら適当に古すぎるのは消そうみたいな運用になっており、まあそれは仕方ないにしてもせめて早い段階で人間がデプロイ完了できないことに気づく仕組みが必要だな……と思った。
いくつか方法はありそうな気がしたけど、ありもので楽に動かせそうなのはGitのなんらかのhookを使って適当なタイミングでチェックすることかなと思い、いくつかあるhookのなかで今回のユースケースに一番合いそうなpre-push hookを使ってやってみることにした。

# 実現方法
## GAEにデプロイされてるバージョン数の取得
あるプロジェクト中のGAEにデプロイされてるサービスについては以下のコマンドで取得できる

```shell-session

% gcloud --project ${PROJECT_NAME} app versions list
SERVICE       VERSION.ID                      TRAFFIC_SPLIT  LAST_DEPLOYED              SERVING_STATUS
xxx           20201102t050314                 0.00           2020-11-02T14:05:47+09:00  SERVING
（略）
```

ので、数は `wc -l` とかに食わせれば出力できる


## フックの実装
ここにmanがある https://git-scm.com/docs/githooks#_pre_push
そしてここにいい感じの解説がある https://stackoverflow.com/a/42462882
これらを参考に作ってみたのがこれ

```bash
#!/bin/bash

check_tag () {
    if [[ "$1" =~ ^stg_ ]]; then
        echo "stgのバージョン数チェックするよ"
        check_n_version $(gcloud --project my-awesome-project-stg app versions list | wc -l)
    elif [[ $"$1" =~ ^prod_ ]]; then
        echo "prodのバージョン数チェックするよ"
        check_n_version $(gcloud --project my-awesome-project-prod app versions list | wc -l)
    else
        exit 0
    fi
}

check_n_version () {
    n_max_versions=190

    if [[ "$1" -ge $n_max_versions ]]; then
        echo "バージョン数多すぎ ($1)" 1>&2
        exit 1
    else
        exit 0
    fi
}

while read localref localhash remoteref remotehash; do
    case $localref in
        refs/tags/*)
            check_tag ${localref#refs/tags/} ;;
        *)
            exit 0 ;;
    esac
done
```

### 解説
シェル芸人でもGit芸人でもないから何やってるかわからなくなりそうなので、難しそうなところちょっと解説しておこう。

```bash
while read localref localhash remoteref remotehash; do
```
ここなんで `while` でまわしてるかというのは上で示したstackoverflowでも言及されてるけど、git pushは1コマンドで複数のrefを送信できるので、全部について見てあげたいから（1つのpushしようとしてるrefにつき、この4つのセットが1行渡される）。

```bash
        refs/tags/*)
            check_tag ${localref#refs/tags/} ;;
```
ここの `${localref#refs/tags/}` というのは `$localref` の先頭から `refs/tags/` を抜き去った文字列を作ってくれる。こうすると `check_tag` のなかでは渡されたのが真にタグの名前というのがわかるのでうれしい。

## 実際に動かしてみる
超雑なタグ名で試してみる。

うまくいくとき。

```shell-session
% git push origin stg_x
stgのバージョン数チェックするよ
Total 0 (delta 0), reused 0 (delta 0)
To github.com:xxxx/xxxxx.git
 * [new tag]         stg_x -> stg_x
```

こっちはダメなとき。

```shell-session
% git push origin stg_x
stgのバージョン数チェックするよ
バージョン数多すぎ (193)
error: failed to push some refs to 'github.com:xxxx/xxxxx.git'
```

うまくpushを阻止することができた。

# できたこと
pre-push hookで自動デプロイの対象になるタグのpushを捕捉してGAEのバージョン数見てあげることで悲しみを減らせるようになったよ

# 今後の課題
- クライアントhookはGitの管理下にないために、各自がcloneしたあとに追加の作業が必要になるので、実際に適用するためにはなんらか手順を整える必要がある
- 運用ルールが整えばタグpushのタイミングで勝手に古いバージョン消すとかもアリかも。権限どうつけとくかとか本番環境も勝手に消えるのかとかちょっと怖いからかっちりテストしたいけど
Flutter TDD Clean Architecture Course その8 - Local Data Source

- [こちらのチュートリアル](https://resocoder.com/2019/09/26/flutter-tdd-clean-architecture-course-8-local-data-source/)を解説します。
- チュートリアルを読むのが早いと思いますが、[概要をまとめた](https://qiita.com/maria_mari/items/8fdd92af13a36c663360)のでこちらもご参照ください。

# 前回まで
- CleanArchitectureの内側から順次実装
    - Repositoryの実装完了
        - Repositoryの実装のためにRepositoryの依存オブジェクトのインターフェイスを作成

# 今回
- Repositoryの依存オブジェクトの1つ、LocalDataSourceの実装を作成
    - インターフェイスは既に作成済み

---

# shared_preferences
- localデータ保存にはshared_prefrencesを用いる

```dart
final jsonString = sharedPreferences.getString('CACHED_NUMBER_TRIVIA');
```

```dart
sharedPreferences.setString(
    CACHED_NUMBER_TRIVIA,
    json.encode(triviaToCache.toJson()),
  );
```

- 保存時に、JSONをStringとして保存する
    - TDDではfixtureを用いる

# getLastNumberTrivia
- ローカル保存されたデータの取得
    - インターフェイスは非同期(Future)であるが、shared_preferencesは同期的に取得する
        - `Future.value()`を用いて非同期化している

```number_trivia_local_data_source.dart
@override
Future<NumberTriviaModel> getLastNumberTrivia() {
  final jsonString = sharedPreferences.getString('CACHED_NUMBER_TRIVIA');
  // Future which is immediately completed
  return Future.value(NumberTriviaModel.fromJson(json.decode(jsonString)));
}
```

# 例外を投げる操作のassert
- 以下のように記述する

```dart
expect(() => call(), throwsA(isA<CacheException>()));
```


```test.dart
test('should throw a CacheException when there is not a cached value', () {
  // arrange
  when(mockSharedPreferences.getString(any)).thenReturn(null);
  // act
  // Not calling the method here, just storing it inside a call variable
  final call = dataSource.getLastNumberTrivia;
  // assert
  // Calling the method happens from a higher-order function passed.
  // This is needed to test if calling a method throws an exception.
  expect(() => call(), throwsA(isA<CacheException>()));
});
```

# 学んだこと
## DataSourceのアクセスメソッドを非同期としている
- DataSourceのインターフェイスは非同期メソッドを宣言する
    - インターフェイスはどのような実装にも差し替えられるように作るため
- 今回仕様したsharedPreferencesは同期的にアクセスするものなので、実装内部で`Future.value()`を用いて非同期化する

## 例外を返すテスト
- 以下のように記述する

```dart
expect(() => call(), throwsA(isA<CacheException>()));
```
AWSでやらかして3桁万円請求された話

# はじめに
こんにちはkappa0923です。
普段はSierでクラウドアーキテクトをしています。

この記事は「[本番環境でやらかしちゃった人 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/yarakashi-production)」20日目の記事です。

今回はAWSの設定をミスったために普段の10倍以上の料金を請求されるに至った経緯と、その対策について記載していきます。

# お断り
この記事はあくまでもノウハウを共有して、同様にやらかす人を少しでも減らすことを目的としています。

記事の内容はあくまで個人の見解であり、所属とは関係ありません。

また、**多少のフェイクを混ぜますので、**あらかじめご了承ください。

# 発生背景
とあるWebシステムをAWS上で構築し、開発・運用を行っていました。

このWebシステムではバッチ処理を並列で行うため、複数の小さいEC2インスタンスを作成→実行→削除という構成をとっていました。
これにより必要ない時間はEC2インスタンスが起動しておらず、ランニングコストを抑えようという狙いがありました。

![AWSでやらかした話.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129736/b0d9f9de-e17b-816c-fc53-8d04309451a1.png)


しかし、この構成が仇となって発覚が遅れ、さらに膨大な費用を請求されるまでに至ってしまいました。

# 何が起きたか
バッチ処理を行うEC2は毎回AMIから作成されるようにしていましたが、あるときにAMIの設定をミスり、EC2インスタンス削除時にアタッチされているEBSボリュームを削除するオプション(DeleteOnTermination)が外れてしまいました。

その結果、EC2インスタンスは削除されるが、**EBSボリュームが削除されないまま残り続ける**という状態が発生しました。

![AWSでやらかした話-Page-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129736/cd617523-31ee-13d6-fd33-6570c7e72f9d.png)



EBSボリューム1つ1つはサイズも小さく、単位料金も安いですが、気づいたときには**1万台を超えるEBSボリューム**が残り続けていました。

結果として、1ヶ月以上設定ミスに気づかず、EBSのストレージ使用料金だけで1ヶ月で3桁万円の請求が来ることになってしまいました。

EC2インスタンスは起動しっぱなしにならないよう、毎日確認していましたが、EBSの方まで気が回っていなかったことは落ち度です。

# 対策
[AWS Budge](https://aws.amazon.com/jp/aws-cost-management/aws-budgets/)を利用して予算設定をする、これに尽きます。

今回は急激に料金が跳ね上がった訳ではなく、徐々に徐々に料金が大きくなっていきました。
つまり、気づくチャンスがあったわけです。

これらを設定するだけで、請求額が異常な値になる前に気づくようになることができます。
このサービス自体は認識していましたし、予算を設定することの大切さも知っていたつもりでした……
しかし、後回しにしてしまったがために今回のようなミスが発生しました。

クラウドに関わらず、設定ミスによるやらかしは往々にして発生します。
だからこそ、**ミスった時に気づけるようにしておくこと**が大切です。

# おわりに
十分注意していたと思っていても事故は起こります。
特にアクセスキー流出などによる請求はよくある話なため、十分に注意していました。
しかし、今回は予想していなかった設定ミスでした。

大切なことは、このような**失敗のバッドノウハウを共有することで一人でも同じやらかしを防ぐ**ことです。

みなさんもこのバッドノウハウを活かし、良いクラウドライフを送っていただけることを祈っています。

明日の[本番環境でやらかしちゃった人 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/yarakashi-production)は@real_yaruoさんの「ホスティングサーバの/binディレクトリをrm -rfしてしまったおじさんの昔話」です。
エミュレーターとBurp SuiteでAndroidアプリのHTTP通信をキャプチャする(Android7.0以上対応)

# 概要

バグハンティングなどでAndroidアプリのHTTP通信をキャプチャする必要があるかと思う。そこで本記事ではAndroidエミュレーターのNoxPlayerとBurp Suiteを用いた環境を構築する。

本記事ではmacOSとNoxPlayerを使用することを前提としているが、それ以外の環境でもAndroidエミュレーターとBurp Suiteを用意できれば同様の方法で環境構築できる（と思う)。

Android7.0からは証明書の扱い方法の変更により、Burp Suiteのクライアント証明書のインストール方法に一手間加える必要がある。本記事ではその設定にも対応する。

# 環境

- macOS Catalina 10.15.7
- Homebrew 2.6.0
- NoxPlayer 3.0.5.0
- Burp Suite Community 2020.12.01

# 手順

## 0. 準備

- homebrew-caskでNoxPlayerをインストールする
- Burp Suiteをインストールする
- Burp Suiteの証明書(`cacert.der`）を用意する
- ホストマシンのローカルIPアドレスを調べる
- 以下の画像のようにNoxPlayerのシステム設定からroot化する

![スクリーンショット 2020-12-06 16.15.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57640/b83350ae-a00f-64cc-e7b3-0d47da5fd48c.png)

## 1. 0.で用意した証明書をAndroidのシステム証明書としてインストールする

以下のコマンドで`cacert.der`をシステム証明書としてインストールできる形式に変換する。

```
❯ openssl x509 -inform DER -in cacert.der -out cacert.pem
❯ mv cacert.pem `openssl x509 -inform PEM -subject_hash_old -in cacert.pem |head -1`.0

9a5ba575.0
```

`adb`を使って用意した証明書ファイル(`9a5ba575.0`)をエミュレーターにインストールする。`adb`はhomebrew-caskでNoxPlayerをインストールした場合は`/Applications/NoxAppPlayer.app/Contents/MacOS/adb`に配置されている。

```
❯ adb push 9a5ba575.0 /sdcard/9a5ba575.0
❯ adb shell

dreamqltecan:/ # su
dreamqltecan:/ # mount -o rw,remount /system
dreamqltecan:/ # mv /sdcard/9a5ba575.0 /system/etc/security/cacerts/ 
dreamqltecan:/ # chmod 644 /system/etc/security/cacerts/9a5ba575.0 
dreamqltecan:/ # reboot
```

## 2. Burp Suiteの待ち受けアドレスを`All interface`に設定する

以下の画像のように設定する。

![スクリーンショット 2020-12-06 16.17.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57640/af0fc507-0cf8-5a5b-c3cf-9142776f1f29.png)

![スクリーンショット 2020-12-06 16.17.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57640/5e889031-089f-7b64-5ad0-0f1342465431.png)

## 3. エミュレーター内のネットワーク設定からホストマシンのBurp Suiteをプロキシサーバーに設定する

0.で調べたホストマシンのローカルIPアドレス(`192.168.3.13`)をここで設定します。

![スクリーンショット 2020-12-06 17.03.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57640/ffcdf75a-cefd-f4c1-3ffe-2440ae5adb9c.png)

## 4. テスト

適当なアプリケーションを起動すると、Burp SuiteでHTTP通信の内容がキャプチャできていることが確認できる。

![burp_cap.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57640/3c479667-9839-1177-ee6b-845cb5701f58.jpeg)


# 参考文献

- [Configuring Burp Suite With Android Nougat](https://blog.ropnop.com/configuring-burp-suite-with-android-nougat/)
PCのパーツとその役割

##記事の目的
- 自作PCの作成のため同僚に秋葉原に連れて行ってもらったそこで学んだことをまとめる

##各パーツ名とその役割

- RAM：「Random Access Memory」（ランダムアクセスメモリ）。"作業"領域を表す。一般的にメモリというとコレ。
- ROM：「Read Only Memory」（リードオンリーメモリ）。"保存"領域を表す。今はSSDが主流。昔はHDDが流行っていた
- CPU：演算処理を行う
- GPU：画像処理を担う。
- マザーボード：電子回路基板。一言でいうとパソコンの土台。CPU・メモリー・ハードディスクなどをつなげる場所
- ファン：処理で熱を持ったパーツを冷やす
- ケース：パソコン内部のパーツを外部からの衝撃やホコリ、湿気などから保護する
【FileMaker】Siriも活用してFileMaker Goにあるアプリを活用しよう

今日も極粒Tipsですが、モバイル感を醸し出せる要素です。
ショートカットアプリを使ってFileMaker Goのアプリを起動するTipsを紹介しました。

[【FileMaker】ショートカットからFileMaker Goのファイルを開くときの設定](https://qiita.com/tyuma/items/4ed2338956ef7307aa39)
[【FileMaker】iOSショートカットアプリからFileMaker Goアプリを開くときの注意点](https://qiita.com/tyuma/items/2d4085a13a540646302d)

ショートカットを使う時、そのまま指でタップしてもいいのですが、Siriを活用すると手が離せない時に都合よくFileMaker Goのアプリを起動できます。
もはやFileMakerのTipsではないのですが、FileMaker Goを使っていく上でiOSの機能は外せないのでちょっとおつきあいください。

# やり方
1.iPhoneに向かって"Hey,Siri"と話しかけるか、iPhoneサイドボタンを長押しします。
2.Siriが起動するので、ショートカットで登録したショートカット名を言います。
![スクリーンショット 2020-12-20 21.36.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60538/7bf3af25-1023-5c17-b586-bdda81016075.png)
3.ショートカットに登録してあるFileMaker Goアプリが起動します。

# 動き
では、画面録画なので声はありませんが、動きをご覧ください。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Siriがいい働きをしてくれます。<a href="https://twitter.com/hashtag/FileMaker?src=hash&amp;ref_src=twsrc%5Etfw">#FileMaker</a> <a href="https://twitter.com/hashtag/FileMakerGo?src=hash&amp;ref_src=twsrc%5Etfw">#FileMakerGo</a> <a href="https://t.co/kfYpSkGsyO">pic.twitter.com/kfYpSkGsyO</a></p>&mdash; Guppy Web Service (@GuppyWebService) <a href="https://twitter.com/GuppyWebService/status/1340641536963354624?ref_src=twsrc%5Etfw">December 20, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 注意点
ショートカットでFileMaker Goアプリを指定する際にも注意点があったように、Siriで動かすときも注意点があります。
まず、

**1.英語名なら英語の構文でかけ！**

です。
どういうことかというと、Siriは"FileMakertest"というショートカット名はスルーしますが、"FileMaker test"と英語の構文のように単語間にスペースを入れて作成すると、スルーせずにFileMaker Goのアプリを起動してくれます。

**2.記号は含めるな！**
こちらも書いたアイコンをタップするだけならちゃんと認識してくれますが、"FileMaker / test"を"ファイルメーカー スラッシュ テスト"と喋ってもスルーされます。

**3.数字を英語で発音するな！**
2と同様、書くだけならいいのですが、"sms送信テスト1"を、"エスエムエスそうしんテストわん"と言うとスルーされるので、"エスエムエスそうしんテストいち"と発音してあげましょう。


モバイル道具として使うFileMaker Goなので、iOSの機能を連携して存分に使いたければiOSのお作法に則って快適に使いましょうね、と言うお話でした。
めでたし、めでたし（めでたいのか！？）
Pythonのファイル読込み時にCRLFがLFになる件


受け取ったテキストファイルにLF、CR、CRLFが混在していて、
CRLFとCRを置換する必要に迫られたとき。

以外とググっても出てこなかったので備忘録として残します。

Python3.7 


# 事例
2行目の末尾にCRLFが存在（^MがCR）。
これを別の文字に置換したい。

```bash
$ cat crlf.txt
1	2	3
4	5	6^M
7	8	9
```

# (×) テキストファイル扱いで開く
Pythonで通常のテキストファイル扱いでOpenすると、
あれ…？置き換えされない？

```python

with open('crlf.test') as f:
    for line in f:
        line = line.replace('\r\n','XXX') #CRLFを置換
        print(line)

# 1       2       3
#
# 4       5       6
#
# 7       8       9
```
どうやら、Pythonが親切にもCRを外してくれるようです。
CentOS上で動作させたGoやC#も同様だったので、OS環境に依存している模様。


# (〇)バイナリファイル扱いで開く
バイナリモードで開けば、勝手に変換されることはないのでは？と思い、やってみた。
読み取ったデータはバイト列なので、デコードして文字列に変換する　のステップが必要。
バイナリだから文字列変換後じゃないと改行を判別できないのでは…
と思いきや、バイナリのLFを改行として認識したうえで1行を返してくれるので楽ちん。

```python

with open('crlf.test','rb') as f:
    for line in f:
        line = line.decode().replace('\r\n','XXX')
        print(line)

# 1       2       3
#
# 4       5       6XXX
# 7       8       9
```
CRLF が XXX になったので、改行も1つ無くなっている。

CRだけ置換するとどうなるか。

```python

with open('crlf.test','rb') as f:
    for line in f:
        line = line.decode().replace('\r','XXX')
        print(line)

# 1       2       3
# 
# 4       5       6XXX
# 
# 7       8       9
```
ちゃんとCRだけ置換され、CRLFのLF部分は残っている。

# 結論
改行コードが混在するテキストファイルを扱うときは、
バイナリモードでテキストファイルを開きましょう
アルゴリズム力UP!『ATC 001 A 幅優先探索 』を解いてみよう

# 記事を読むとわかること
- 記事の作者の『ATC 001 A 幅優先探索 』の解法がわかる
- 記事の作者のアルゴリズム問題全般へのアプローチがわかる

# 今回の問題 

**ATC 001 A 幅優先探索**　→ [問題文はこちら](https://atcoder.jp/contests/abc007/tasks/abc007_3)

# 記事の作者の回答のコード
```python
from collections import deque

# ==============================================================
# return: スタートのマスを代入すると、スタートからゴールまでの最短手数を出力
# pre, post: 問題文に記載 -> 2 3 のマスの解釈を 3行目の2列目にあるマス というのではなく、2次元配列の2行目の3行目にあるマス　と最初考えた方が混乱しないかも。

# ==============================================================
# ★ step1 問題文を解くために必要な迷路をインプットする
# ★ step2 幅優先探索でスタートからゴールにむかい上下左右に探索をさせる。
# ★ step3 探索を進む際に、これから進むマス = 前回進んだマスのスタートからの手数 + 1 をし、マーキングをする。
# ★ step4 ゴールにたどり着いた際、ゴールのマス時点でのスタートからの手数を返す


#問題分の行と列の数をインプット
r, c = map(int, input().split())
#スタートのマスの場所をインプット
sx, sy = map(int, input().split())
#ゴールのマスの場所をインプット
gx, gy = map(int, input().split())
sy -= 1
sx -= 1
gx -= 1
gy -= 1
#r個分ある迷路の描画を一列ずつインプット
maze = [list(input()) for i in range(r)]


#幅優先探索
def bfs():
    # 上下左右の4方向に分岐させる (今回は1行目の0行目みたいな解釈になるので、どの方向かは厳格に考えない方が無難？)
    dx = [1, 0, -1, 0]
    dy = [0, 1, 0, -1]

    #探索をする際にスタートからの手数を記憶するために、別で新たに迷路と同じ大きさのモノを用意。
    d = [[float("inf")] * c for i in range(r)]
    #キューの作成
    que = deque([])
    #スタートのマスをいれる
    que.append((sx, sy))
    #スタートを0とする(今後マスを進めるたびに加算していく。)
    d[sx][sy] = 0
    
    while que:
        #先入先出しで四方向に順番に検索していく
        p = que.popleft() 
        #popしたものがゴールか判断
        if p[0] == gx and p[1] == gy:
            break
        #四方向の指定
        for i in range(4):
            #四方向にすすむ     
            nx = p[0] + dx[i]
            ny = p[1] + dy[i]
            #指定いる座標が迷路内か判定
            if 0 <= nx < r and 0 <= ny < c :
                #指定した座標が塀ではないか判定
                if maze[nx][ny] != "#" :
                    #指定した座標が未開拓であることを判定
                    if d[nx][ny] == float("inf"):
                        que.append((nx, ny))
                        #新しく開拓する場所に、既に開拓した場所の値を+1し代入させる
                        d[nx][ny] = d[p[0]][p[1]] + 1
    #popした際の進んだ値を返す
    return d[gx][gy]

print(bfs())
```
# 回答の解説

## 記事の作者が思う今回のアルゴリズム問題の回答ポイント

- 迷路やスタート及びゴールを入力する際、迷路が配列で扱う関係で０番目スタートであることを意識
- 幅優先探索であるため、先入先出しのためのpopleftメソッドを使う
- 条件分岐として、迷路の範囲内か、塀か、未探索かを判断する
- 探索先に前回探索した時のスタートからの手数+1をする

#このコードの課題感

- 今回の前提として〜行目の〜行目のマスとしたが、仮に「2行２列目」がスタートであると解釈する場合、今回の変数x(列),y(行)がちょっと気持ちが悪くなる
ffmpegで.h264を構成するフレーム枚数を推測する

# これは何？
Raspberry Piのカメラモジュールから標準の`raspivid`で取得した動画は`*.h264`拡張子で保存されます。
これはRaspberry Piの外の環境では使い勝手が悪く[^1]、都度`.mov`や`.mp4`に変換することになります。
`.mp4`など使い勝手がいい形式にエンコードをかける過程でフレームレートの指定をするのですが、その逆算に必要な動画を構成するフレーム数の確認が必要になり、これが手軽に行えないかなと思い表層的な情報をさらった試行錯誤の記録です。


注意として、まだ私のメディアファイルのデータ形式についての理解が今ひとつなので話半分でみてもらえたら幸いです。詳しい方による解説等もいただければと思います。


# ffmpegとは
Win/Mac/その他多くのUNIX系OSに対して提供されているメディアエンコーダ(として使える)ソフトウェアです。
バイナリをダウンロードしてきてPathを通すような感じで使います。(macだと`Homebrew`,Linuxだと`apt`なんかでのインストールが便利！)

`.h264`のファイルはffmpegを単純に使うならば、

```Bash:CLI
ffmpeg -i input.h264 output.mp4
```

とすれば `.mp4`にエンコードできます。[^2]




<details><summary>ちなみに...</summary><div>
似たようなエンコード処理を行えるソフトウェアとしてGPACの`mp4box`や`L-SMASH`等があります。
携帯動画変換君の時代以降、動画をエンコードする需要があり様々なGUIベースのエンコーダが提供されてきましたが、これらを内包して稼働させているものも多くありました(もちろんライセンスの規約内で)。
なお、`.h264`を変換なしで確認しなければいけない界隈の常套策は`VLC Media Player`で読み込むことですが、これはどうやらh264を内部でエンコードして表示してるっぽいですね。
</div></details>


# 本題:ffmpegで.h264を構成するフレーム枚数を推測する

ffmpegの取得で同時に使えるようになる**ffprobve**を使うと動画の構成情報が調べられます。

```Bash:CLI
ffprobe -i input.mp4 
```

ただしこれは`.mp4`に限った話で、`.h264`の動画はコーデックに付随した情報をうまく取得してくれません。

そのため、例えば、動画を構成する全ての静止画(=frame)に対して一枚ずつ解析をかける`-show_frames
`オプションを利用すると無理やり数え上げを行うことができます。
すべてのフレームの解析情報がコンソールに表示されるととても困るので、適宜出力を捨てながら、

```Bash:CLI
ffprobe -i input.h264 -show_frames |grep -c '^\[FRAME'
```
のようにすると`.mp4`のようにエンコード先の実体を保存しなくてもフレーム数を知ることができます。[^3]


外部の質問サイトでも似たようなことをしているサイトがあるようですので合わせてご覧ください。

[video - склеить - Считать количество кадров ffmpeg ](https://code-examples.net/ru/q/1eca33)

以上、自分用メモでした。


---

[^1]: `.h264`形式の動画ファイルはどうやらフレームレートなど再生情報などをファイル内に保持しておらず、これが悪さをするらしい。

[^2]: 実際にはオプションをきちんと記述した方が効率よくエンコードがかけられる。<br>なお、1920x1080 で15fps 45Mbpsの10分間の動画で20分ぐらいかかってます。なお、mp4boxだとデフォルトの設定でも2分くらいでエンコードできるので謎だ...


[^3]: コンソールへの出力が鬱陶しいならば`2>&1`みたいにさらに削ってもいいでしょう。
Cosmos DB : サンプルデータを簡単に作成する

今回は Cosmos DB でサンプルデータを GUI だけで簡単に作成する方法を紹介します。

# 前提

- Cosmos DB アカウント
- [コンパイル済 Data migration tool](https://aka.ms/csdmtool) または [GitHub のソース](https://github.com/azure/azure-documentdb-datamigrationtool) をダウンロードして自分でコンパイル
- [Azure Cosmos DB サンプルデータ](https://github.com/Azure-Samples/azure-cosmos-db-sample-data)

# Cosmos DB のセットアップ

まずは Cosmos DB アカウントを作成し、データベースとコレクションを追加します。ここではサンプルデータの名前に合わせて、volcanodb および volcanocollection という名前にしました。

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/wubpz4egw3221zj7qpmi.jpg)

# Data migration tool でサンプルデータをインポートする

Data migration tool は GUI と CUI の両方がありますが、ここでは GUI 版を使います。

1\. ツールをダウンロードして任意のフォルダに解凍。

2\. フォルダより dtui.exe を実行。

3\. [Add files] をクリックして VolcanoData.json を選択。
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/c79jen4opi690j9w3jgh.jpg)

4\. Cosmos DB の画面で接続文字列を確認。
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/oi3sgmgvjr34tpmofy5u.jpg)

5\. 接続文字列の最後に "Database=volcanodb" を付けて、Connection 欄にペースト。Verify をクリックして接続確認。
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/jzsp1hzj4f4ynp6769fa.jpg)

6\. コレクション名を指定して、ウィザードを完了。

全てのステップが完了すると、インポートが自動で行われます。
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ebwiyxet98avdkj0tks0.jpg)

# まとめ

多くのデータは CSV や JSON で手に入るため、GUI ツールで簡単にデータを入れれるのは助かりますね。是非お試しください！

# 参照
[Tutorial: Use Data migration tool to migrate your data to Azure Cosmos DB](https://docs.microsoft.com/en-us/azure/cosmos-db/import-data)
[Azure Cosmos DB Sample Data](https://github.com/Azure-Samples/azure-cosmos-db-sample-data)
自動テストが根付かない

プロジェクトに自動テストを導入しようとして、失敗を繰り返した体験談です。

## 前提
テストケースはエクセルで管理され、QAがマニュアルで実施している。
リリースから時間が経過しており、機能もテストケースも肥大化。
テスト工数が大きい関係でリリース間隔は長め。
自動化テストを導入すべき、という声は定期的に上がり、
それらの技術についてプレゼンしても反応は悪くないが、実際に導入すると定着しない。

## 原因（と予想しているもの）
### 一応対処できたもの
##### テストの実行が手間
対策として自動実行を検討。
タイミングとして以下の３つを検討した結果、３番目を採用。
１． [見送り]git hookでコミット実行実時にテスト差し込み、テスト通ったらコミット処理
各人のローカル開発環境に干渉する必要がある。
テストケースの実行時間に制限が出てくる。

２． [見送り]Pull Request作成時にテスト開始、テスト通ったら結果をPull Requestにコメント反映
コミット時よりは緩いが、それでもテストケースの実行時間に制限が出てくる。

３． [採用]マージ時にテスト開始、結果を失敗したときのみチャットに通知
リモートで完結するため、開発者のアクションを妨げない。
代わりに検知が遅くなるので、修正が遅くなりがち。

##### コードがテストしにくい書き方になっている
数千行のクラス、数百行の関数など。
そのためテストが大変書きづらい。
既存コードすべてをリファクタリングすることは現実的ではなかったため、対策として以下２点を実施。

１．自然言語で記述できるBDDフレームワーク採用
ケースの作成は楽になったものの、後述のテストを書く時間の問題によりケースが増えず廃止。

２．画面のscreenshotによる画像差分テスト採用
ある程度のバグの事前検知に貢献。
ただし画面全体に対するスクリーンショットだったため変化に非常に弱く、
期待値の更新を頻繁に行わなければならないというメンテナンスコストの高さと、
後述する時間がないことによるテスト失敗状態が続いたことが重なり運用停止。

### 対処できなかったもの
##### 機能実装で手一杯でテストを書く時間がない
そもそも開発が遅れることが多く、テストを書く余裕がない。
また、既存テストが壊れた場合も治す余裕がなく、自動実行と通知の仕組みがあっても、後回しにしてしまい、
結果としてテスト失敗通知を無視するようになってしまう。
自動テストを導入することで長期的には生産性は上がるはずが、
移行時の生産性低下を乗り越えられない。

##### 開発者自身があまりテストをしないため、自動テストを書くモチベーションが沸かない
楽をするためにがんばる、というのをモチベーションにできない。
品質向上、生産性向上、という名目は分かる。
※これは原因としては弱め。

## 振り返りと次回挑戦時の注意点
・興味のない人には不便を掛けず、興味ある人は移行できるように、というのはいい人ぶろうとしすぎた。
　他の人を説得するコストを惜しむべきではなかった。
・もう少し定量的なデータを集めて説得材料を作るべきだった、かもしれない。
　- 現在の手動テスト実行工数（これはある）と自動化後の手動テスト実行工数＋自動テスト実行工数
　　→手動テストをすべて置き換えるものではない、としか説明していない。
　- 自動テストで品質を上げることで生産性が上がるので結果的にテストを書く時間はロスにならない、という根拠
　　→交差するグラフはよく見るけど実績値があるか
　- 自動テストのメンテナンスコスト
　- 学習コスト


※この記事は事実を基にしたフィクションです。実在の人物、団体とはあまり関係ありません。
【個人開発】プライバシーポリシー、、、めんどくさいですよね？【iOS/Android】

# 個人開発で一番めんどくさいこと
プライバシーポリシーの作成とホスティングですね！

個人開発で一番めんどくさい部分、プライバシーポリシーをどうつくって、どうホストするのか
法律のことよくわからんし、こちとらネイティブが本職だってのにウェブにわざわざ設置しなくはなりません

## そんなあなたに！自動で作成＆ホスティングしてくれるツールを紹介
[App Privacy Policy Generator](https://app-privacy-policy-generator.firebaseapp.com/)

これをつかうと、空いている項目に入力するだけで、

![スクリーンショット 2020-12-20 21.38.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138063/1eb6fc9a-6a49-bbc5-6e2f-5dda54d891dc.png)

こんなふうに、いい感じのプライバシーポリシーを作成してくれます！
![スクリーンショット 2020-12-20 21.40.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138063/b4ea47eb-4731-b692-bb53-bfb047004055.png)

これでアプリ開発に専念できますね！

## まとめ
めんどくさいプラポリは生成ツールにまかせて、個人開発を楽しみましょう！