OmniAICreator/Qiita-1.07M huggingface dataset:
【SpringBoot】開発環境作成（BitBucketプッシュ）

SpringBootの開発環境作成2回目です。
今回はプロジェクトのデータをSourceTree（Git管理ツール）を使ってBitBucketにプッシュするまでやっていきます。

## 作成内容
[①Springプロジェクト作成](https://qiita.com/nmaolks/items/ac3860d78822d52bb81c)
**②BitBucketプッシュ**
③[VPSにデプロイ](https://qiita.com/nmaolks/items/200fc9422b455863ac81)
④Jenkins自動デプロイ

## SourceTree
SourceTreeはGitの操作をGUIで分かりやすく操作できます。
インストールについては割愛します。


Createで新規タブを作成。
前回作成したプロジェクトを「参照」で指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1234354/32db400c-5618-f42d-a70f-b2917ea4d5aa.png)

一度コミットします。現状ではBitBucketに連携できていません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1234354/633bea6b-aa4d-e26b-db17-6398edafd609.png)


## BitBucket
こちらも登録方法等は割愛いたします。
以下内容でリポジトリ作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1234354/43410a6f-cc3c-8f6f-fa1b-fb50527e5fbd.png)

BitBucketの新規リポジトリ作成後のページで以下コマンドが用意されてます。
アプリパスワード設定されている場合は、`USER@`の部分を`USER:APP_PASS@`に変更する必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1234354/e43d2e15-6397-58ae-64e9-0b104dde32a0.png)

## 再度SourceTreeを操作
ターミナルから上記のコマンドを実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1234354/54bcc2c3-0576-d6ca-1336-68bc3b42d2c2.png)

赤枠のプッシュをすることで黒枠の`origin/master`となりBitBucket連携完了となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1234354/d2c38098-4841-ad4d-629b-19b94d68c998.png)
競プロ解法メモ


## はじめに

競プロを解いていて、奇跡的に自分で思いつけた解法を記録しておく。
忘れないようにするのが１番だが、どこにも記録しないのは自分が困るのでメモする。


## f(b)-f(a) を行う

a番目～b番目までといった特定範囲に含まれる何かを膨大な回数求めるときに用いる。
上手く日本語にできないが、以下のような問題を解くときに使う。

> A, C, G, T からなる長さ N の文字列 S が与えられます。
> 以下の Q 個の問いに答えてください。
> 
> 問 i (1 ≤ i ≤Q): 整数 li​ ,ri (1 ≤ li <ri ≤N) が与えられる。
S の先頭から li​ 文字目から ri​ 文字目までの (両端含む) 部分文字列を考える。
この文字列に AC は部分文字列として何回現れるか。

問題引用元
[ABC122 C](https://atcoder.jp/contests/abc122/tasks/abc122_c)

上記の場合、毎回 li から ri に含まれる AC を求めていると時間がかかりすぎるので、事前に i 番目までに含まれる AC の数を求めておく。
i 番目までに含まれる数をf(i)とする。
そうすることで、Q 個の問いを解くときに、 f(ri)-f(li) を求めるだけでよくなる。

実際に上記の問題を解いた時のコードは以下となる。

```c++
#include <iostream>
#include <vector>
#include <string>

using namespace std;

int main()
{
	long n, q;
	cin >> n >> q;

	string s;
	cin >> s;

	vector<long> sl(n); // iまでに含むACの数を記録
	long cnt = 0;
	for (long i = 0; i < n - 1; i++)
	{
		if (s[i] == 'A' && s[i + 1] == 'C') cnt++;
		sl[i + 1] = cnt;
	}

	vector<long> ans(q);
	long l, r;
	for (long i = 0; i < q; i++)
	{
		cin >> l >> r;
		ans[i] = sl[r - 1] - sl[l - 1];
	}

	for (long i = 0; i < q; i++) cout << ans[i] << endl;
}
```
【yocto】特定ライセンスのソースコードのみを取り出す方法

# はじめに
- OSSライセンスに準拠するために、開示するソースコードを取捨選択したい人向け
    - GPLライセンスのみ開示したい
    - CLOSEライセンスだけ開示したくない等

# 方法
- 以下の文をlocal.confに追記することで、tmp/deploy/sourcesの下にソースが展開される
~~~
INHERIT += "archiver"
~~~
- 以下の文を追記することでOSSライセンスのフィルタ処理が行われる
    - COPYLEFT_LICENSE_INCLUDEは、tmp/deploy/sourcesに展開しないOSSライセンスを指定する。
    - COPYLEFT_LICENSE_EXCLUDEは、tmp/deploy/sourcesに展開しないOSSライセンスを指定する。
    - 同時に記載が合った際には、COPYLEFT_LICENSE_INCLUDEよりCOPYLEFT_LICENSE_EXCLUDEが優先的に採用される。
~~~
COPYLEFT_LICENSE_INCLUDE = 'licence-name'
COPYLEFT_LICENSE_EXCLUDE = 'licence-name'
~~~

# 例
#### `INHERIT += "archiver"`の動作を確認する
- 通常のbitbakeでは、tmp/deployの下にsourcesはない。
~~~
takeshi@X250:~/yocto/poky$ source oe-init-build-env 
(略)
takeshi@X250:~/yocto/poky/build$ bitbake core-image-minimal
(略)
takeshi@X250:~/yocto/poky/build$ ls tmp/deploy/
images  licenses  rpm
~~~

- `INHERIT += "archiver"`を追記することで、tmp/deploy/sourcesが生成される
~~~
takeshi@X250:~/yocto/poky/build$ cat conf/local.conf | tail -n 1
INHERIT += "archiver"
takeshi@X250:~/yocto/poky/build$ bitbake core-image-minimal
(略)
takeshi@X250:~/yocto/poky/build$ ls tmp/deploy/
images  licenses  rpm  sources
~~~

-　各ディレクトリ下はソースコードがtar.gz形式で固められて置かれている
~~~
takeshi@X250:~/yocto/poky/build$ ls tmp/deploy/sources/allarch-poky-linux/ca-certificates-20190110-r0/
ca-certificates-20190110-r0-patched.tar.gz
~~~

#### ライセンスフィルタの動作を確認する
- デフォルトでは、GPL系ライセンスのソースコードのみがtmp/deploy/sourcesに置かれ、独自ライセンスのソースコードは開示しないように設定されている。
~~~
takeshi@X250:~/yocto/poky/build$ bitbake -e core-image-minimal | grep "COPYLEFT_LICENSE"
# $COPYLEFT_LICENSE_EXCLUDE [3 operations]
COPYLEFT_LICENSE_EXCLUDE="CLOSED Proprietary"★
# $COPYLEFT_LICENSE_INCLUDE [3 operations]
COPYLEFT_LICENSE_INCLUDE="GPL* LGPL* AGPL*"★
        include = oe.data.typed_value('COPYLEFT_LICENSE_INCLUDE', d)
        exclude = oe.data.typed_value('COPYLEFT_LICENSE_EXCLUDE', d)
~~~

- そのため、通常ビルドでは、core-image-minimalにzlibパッケージが含まれているにもかかわらず、tmp/deploy/sourcesに置かれていない。
~~~
takeshi@X250:~/yocto/poky/build$ cat tmp/deploy/licenses/core-image-minimal-qemux86-64-20220612125700/license.manifest | tail -n 5
PACKAGE NAME: zlib
PACKAGE VERSION: 1.2.11
RECIPE NAME: zlib
LICENSE: Zlib

~~~
~~~
takeshi@X250:~/yocto/poky/build$ ls tmp/deploy/sources/x86_64-poky-linux | grep "zlib"
takeshi@X250:~/yocto/poky/build$ 
~~~

- Zlibライセンスを含めるように設定して、tmp/deploy/sourcesの下にzlibパッケージが置かれるかを確認する。
~~~
takeshi@X250:~/yocto/poky/build$ cat conf/local.conf | tail -n 1
COPYLEFT_LICENSE_INCLUDE = "GPL* LGPL* AGPL* Zlib"
~~~
~~~
takeshi@X250:~/yocto/poky/build$ rm -rf tmp/
takeshi@X250:~/yocto/poky/build$ bitbake core-image-minimal
(略)
~~~
~~~
takeshi@X250:~/yocto/poky/build$ ls tmp/deploy/sources/x86_64-poky-linux | grep "zlib"
zlib-1.2.11-r0
~~~
Vueチュートリアル PrismとOpenAPI仕様書によるモックサーバーを立てる

### ゴール

- Prismを使ってAPIのモックサーバーを用意する
- VueからモックAPIにアクセスする

## Prismの環境設定

Prismもnpmで公開されているものですが、こちらはプロジェクトに対してインストールするのではなく、
グローバルにインストールすることでPrismを`prism`コマンドからどこからでも利用可能になります。

```bash
$ npm install -g @stoplight/prism-cli
```

PrismでモックするAPIのインターフェースを定義したyamlファイルや、
`paths`と`components`もあればまとめてプロジェクトのルートディレクトリに`api`を作りそこに保存しましょう。

```text
.
|-- api
|   |-- api-spec.yaml
|   |-- components
|   |   |-- parameters
|   |   |-- responses
|   |   `-- schemas
|-- その他
```

配置したら、先ほどインストールした`prism`コマンドでモックサーバーを実際に叩きます。
以下のような形でコマンドを実行します。

```bash
$ prism mock ./api/api-spec.yaml
```

そうすると、モックサーバーとして立ち上がったAPIの各エンドポイントと、
モックサーバーが立ち上がった旨がログに表示されるはずです。
このそのうちのGETのエンドポイントに試しにCURLでアクセスしてみましょう。

```bash
$ curl -s "http://127.0.0.1:4010/pet/-4278430582542102500"
```

うまく動きますでしょうか。
値によってはPrism側からエラーが返されることがあります。
そういうときは落ち着いてエラー文を読み対応可能です。
私が遭遇したエラーとしては、今のこところ以下の2エラーでした。

1. Unauthorized Error ⇒ ヘッダーに`api_key`オプションを加える（`-H 'api_key: special-key'`）
1. Unprocessable Entity Error ⇒ URLをダブルクォーテーションで囲む

あとはこのモックサーバーに対してVueのアプリケーションからアクセスすればOKです。
アクセスにはAxiosというライブラリを使います。

https://axios-http.com/docs/intro

## 環境変数の設定

開発環境時にはPrismモックサーバーである`http://120.0.0.1:4010`、
本番用のビルドでは本番のサーバーである`https://your.api.com`にアクセスをするように設定したいとします。
これを実現するために、環境変数を設定します。

今回はViteを使っていることを前提とします。
Nuxt2を利用している場合はこちらの記事を参考にしてください。

https://qiita.com/TakahiRoyte/items/c152ad8baa191ed1f8ae

Viteは、いくつかの環境変数をJavaScript内の`import.meta.env`オブジェクトに公開します。

https://ja.vitejs.dev/guide/env-and-mode.html

例えば、`import.meta.env.MODE`は`npm run dev`で実行時は`development`、
`npm run build`でビルドしたコードは`production`となるわけです。
この動作を利用して、

Axiosをプラグイン化してbaseUrlの設定を`development`の場合と`production`の場合によって書き換える形です。

https://www.blog.danishi.net/2021/10/29/post-5462/
【JavaScript/TypeScript】?.・?・|

最近使い始めたTypeScriptで気になったものがあったので調べました。TypeScript固有かと思ったらJavaScriptでも使えるものもありました。


# ?.　オプショナルチェーン
※TypeScript/JavaScriptで使用可能

プロパティの最後に付与することで、プロパティ参照を安全にできる。
目的のオブジェクト・プロパティにアクセスする前に暗黙的に`null` または `undefined`かどうかを確認する。(値が`null`か`undefined`なら`undefined`が返る)

特にネストしたオブジェクト・プロパティの`null`・`undefined`チェックをif文よりも簡潔に書くことができる。
```ts
interface Contact {
  emailAddress: string,
  phoneNumber: string,
}

interface Person {
  id: number,
  name: string,
  contact: Contact
}

const person: Person = undefined

const emailAddress1 = 
  person == null || person == undefined ? undefined
: person.contact == null || person.contact == undefined ? undefined
: person.contact.emailAddress;

//emailAddress1よりも簡潔に書くことが出来る
const emailAddress2 =  person?.contact?.emailAddress 
```


# ?　オプショナルプロパティ
※TypeScriptのみ使用可能

プロパティの最後に付与することで任意プロパティとすることができる。
例えばアンケートで入力不要な項目など、必須ではないプロパティに付与することでコードの記述量を減らすことができる。
```ts
interface Enquete {
  name?: string,
  sex: string,
  age: number,
}

function getEnquete(enquete: Enquete) {
  //...
}

//nameプロパティの指定が不要になる
const enquete1 = getEnquete({ sex: "female", age: 20 }); 
const enquete2 = getEnquete({ name: "Bob", sex: "male", age: 25 });
```

# |　ユニオン型
※TypeScriptのみ

ユニオン型は1つの値に対してなりうる型を複数指定することができる。複数の型を許可しつつも、型に制限をかけたいときなどに使用する。

```ts
type Car = {
  //...
};

type Train = {
  //...
};

type Airplane = {
  //...
};

type Vehicle = Car | Train | Airplane;

let vehicleList: Vehicle[] = [
  //Car/Train/Airplaneしか配列に追加できない
];
```

## 参考
- [オプショナルチェーン (optional chaining)](https://typescriptbook.jp/reference/values-types-variables/object/optional-chaining)
- [Optional Properties](https://www.typescriptlang.org/docs/handbook/2/objects.html#optional-properties)
- [Union Types](https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#union-types)
新米エンジニアによるリファクタリングのすすめ-前編-


## はじめに

「先輩からリファクタリングしろって言われたけどどこを直したらいいか分からない...」
とか
「自分の書いたコード全てがクソコードに見える...」

といった悩みを、誰しも一度は感じたことがあると思います。
自分もその1人で、1年間エンジニアとして働く中で、たくさんのコード修正と格闘してきました。
今回は、先輩から聞いたり、本で勉強したりして、自分なりに身につけた「**こういうコードはやばい！**」や「**このタイプのコードはこう直せ！**」といった感覚をまとめてみました。

誰かがリファクタリングする際に少しでも参考になると嬉しいです。拙い記事ですがよろしくお願いします。
1記事にまとめて書くと長過ぎるので、前編・後編に分けます。

前編では
- 破滅的な命名
- 汎用的すぎる命名
- 複雑な条件分岐
- 長すぎるメソッド

後編では
- 再代入を繰り返す変数
- 重複したコード
- 統一化されてないコード
- 不要なコメント

について説明します。

## 注意事項

以下のソースコードは全てRubyで記述しています。
また今回のリファクタリングが最善とは限りません。あくまでも参考として留めていただければ嬉しいです。

## リファクタリングをする前に、まずはテストコードを書こう

「え、リファクタリングの話するって言ってたのにいきなりテストの話？」

と思った方がいるかもしれませんが、**リファクタリングにおいてテストは非常に重要です。**
そもそもリファクタリングとは
> ソフトウェアの外部の振る舞いを保ったままで、内部の構造を改善していく作業(*1)
> 
です。

特に『**ソフトウェアの外部の振る舞いを保ったままで**』の部分が非常に重要で、
「リファクタリング頑張ったおかげで、コードが読みやすくなったし、拡張性も上がったけど、仕様漏れやバグが出ちゃってるなあ」
となってはなんの意味もありません。

その行動はリファクタリングではなく、ただコードを破壊しているだけです。

自分のリファクタリングでコードが壊れていないかを確認するために、**仕様を充分にみたしているか確認してくれるテストコードが必要不可欠です。**

**1箇所リファクタリングしたら、テストコードでバグが起きていないか確認しましょう。**
この作業をサボると、後で泣くハメになります。

> リファクタリングは価値のあるツールですが、単体で成り立つものではありません。なんらかの誤りは必ずあり、それらを指摘してくれる堅牢なテストツールが、適切なリファクタリングには必要です。(*1)
> 

(*1)[リファクタリング 既存のコードを安全に改善する（第2版）](https://www.amazon.co.jp/dp/B0827R4BDW)から引用

## **リファクタリングの必要性を感じるコード**

以下から、怪しい香りのするコードについて具体的に解説していきます。

### **破滅的な命名**

```ruby
class Class1
  def method1
    # 何らかの処理
  end

  def method2
     # 何らかの処理
  end

  def method3
     # 何らかの処理
  end
end
```

最悪です。読む気が完全に失せるコードと言っても過言ではありません。

流石にここまで全ての命名に気を配らない人は見たことないですが、一部コードに似たような命名をしてしまったことがある人もいるのではないでしょうか。
自分も過去にやらかしたことがあります。

**上記のような連番命名は、処理の内容や意図が全く読み取れないので絶対にやめましょう。**
名付けが簡単ということ以外メリットが皆無です。
**何をしている処理なのか、何に使われるのか、何のためのコードなのかなどが伝わる命名が理想です。**
名前だけで、処理や利用方法、目的が伝わる命名を目指しましょう。

### **汎用的すぎる命名**

```ruby
def get_data
  data = User.find(1)
  info = data.name
  number = data.age
  # 何らかの処理
end
```

上記の内容と少し被りますが、命名に汎用的な単語をつけるのも避けた方が良いです。
汎用的な単語からは読み取れる情報が少ない上、誤解を招きやすいです。

**できるだけ具体的な単語を使うことで、処理の目的や変数の中身が明確になります。**

スコープが非常に短いローカル変数など、特殊な場合を除いて汎用的な命名は避けしょう。

### **複雑すぎる条件分岐**

```ruby
if (条件A)
  # 
  # なんらかの長い処理
  #
  if (条件B)
    # 
    # なんらかの長い処理
    #
    if (条件c)
      # 
      # なんらかの長い処理
      #
    else
      # 
      # なんらかの長い処理
      #
    end
  end
end
```

読む気が完全に失せるコードpart2です。
条件分岐のネストを深くすることや、各ブロックの中で長い処理を書くことはコードの読みやすさを大きく損ないます。

また、**条件分岐のロジック理解も困難になるため、コード修正に無駄に時間がかかったり、変更漏れや予期せぬバグを生むことにもつながります。**

このような場合は**ガード節を利用し、早めに処理を抜けることでネストを浅くすることができます。**(*2)
それによりコードの可読性が上がるだけでなく、条件分岐への理解が簡単になり、修正しやすくなります。

(*2)ガード節については[以下の記事](https://qiita.com/kouyan/items/7b8b456b626447a1e24e)が非常にわかりやすいのでおすすめです。

### **長すぎるメソッド**

```ruby
def long_method
  # 処理を説明するコメント1
  # ~
  # 処理数十行
  # ~
  #
  # 処理を説明するコメント2
  #
  # ~
  # 処理数十行
  # ~
  #
  # 処理を説明するコメント3
  #
  # ~
  # 処理数十行
  # ~
end
```

ダラダラとロジックがベタ書きされたメソッドは、コードの可読性を大きく損ないます。
またメソッドが長くなると、どこからどこまでがどんな処理をしているかを把握しづらくなります。
それゆえ、修正する際に該当箇所を見つけることが困難です。

**このようなコードは、意味のあるまとまりでメソッドとして切り出しましょう。**

上記コードのように処理に関するコメントが必要だと感じたタイミングで、**コメントする代わりにわかりやすい命名をしたメソッドに分割するとだいぶ読みやすくなります。**

また、メソッドの命名を適切に行なっていれば、**その目的外の処理を関数として分割する**と考えても、メソッドの切り出しがやりやすいかもしれません。

## **終わりに**

今回はリファクタリングの必要性がある怪しいコードと、それらを直す際の考え方についていくつか紹介しました。

後編ではまたいくつか別パターンの怪しいコードを紹介するので、読んでもらえたらありがたいです🙏
【binding.pry】エラーは出ないけど、画像投稿出来ない時の対処法

# 前提
この記事のVS codeにインストールした`rails6.0.0`を使用しています。
私のオリジナルアプリケーション(躓いたこと)を用いて、誰かの役に立つ記事になればと思います。

# 結論
`binding.pry` で一時停止をして、パラーメータをチェックする。
※Gemfileに「gem 'pry-rails'」を記入し、「bundle install」を行う必要あり。
MVCを理解し、間違いを探す方法を習得する方法を習得する。

# アプリケーション概要
私は写真記録のオリジナルアプリを作成しました。
ユーザー登録をしているメンバーで日本一周の写真を記録するためのアプリケーションになります。

# 内容
投稿しようと思ったが、情報が投稿されない。

```rb:投稿処理
 def create
   @prototype = Prototype.new(prototype_params)
    if @prototype.save
      redirect_to root_path
    else
      render :new
    end
  end
```

どうしてもelseに入ってしまい、saveができない。
条件式に入る前に「binding.pry」で一時停止し、パラメーターを確認する。
`@prototype` の中身を確認すると・・・

```rb:@prototypeの中身の確認
[2] pry(#<PrototypesController>)> @prototype
=> #<Prototype:0x00007f7e0a7960c8
 id: nil,
 title: "hoge1",
 prefecture_id: 2,
 created_at: nil,
 updated_at: nil>
```

imageが無い・・・
paramsの中身も確認すると・・・

```rb
[1] pry(#<PrototypesController>)> params
=> <ActionController::Parameters {"authenticity_token"=>"WY1QbFTGg8VbpOKOhEZF4tSjk05Sy4T5YzAhWYbnEZgmriZ8GaI2764yXaVsaVJ9eZFK6f7i3iQ6Q+5sHCnABQ==", "prototype"=><ActionController::Parameters {"title"=>"hoge1", "prefecture_id"=>"2", "image"=>#<ActionDispatch::Http::UploadedFile:0x00007f7e097b6bd0 @tempfile=#<Tempfile:/var/folders/bd/d59l1pvn1jqdqlv9lshrsswh0000gn/T/RackMultipart20220610-15050-3h6jx4.jpg>, @original_filename="IMG_7163.jpg", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"prototype[image]\"; filename=\"IMG_7163.jpg\"\r\nContent-Type: image/jpeg\r\n">} permitted: false>, "commit"=>"投稿する", "controller"=>"prototypes", "action"=>"create"} permitted: false>
```
`params`の中には、imageが入っているので`private`で何か間違いがあるか？
結局はmodelの記述に問題がありました。

最後に改めて、「MVCを理解し、間違いを探す方法を習得する方法を習得する。」これを強く感じました。
Serverless FrameworkでLambda関数を構成管理

# Serverless FrameworkでLambdaを管理

https://qiita.com/bebeken/items/739a3128f18b7e9c22b1

以前、Lambda関数を作った時に、気になっていたServerless　Frameworkで構成管理してみることにしたのでメモです。
Infrastructure as Codeで、ansibleのようにコードでインフラ構成管理していくイメージです。

## install
nodejsが実行できる状態で、`npm install -g serverless`
https://www.serverless.com/framework/docs/getting-started

## プロジェクト作成
`serverless create --template aws-python3 --name serverless-check-audrey`

実行すると、`serverless-check-audrey`ディレクトリ内に必要なファイルが作成される。

* serverless.yml : 設定を記述していくファイル
* handler.py : 実際にLambdaで実行するプログラム

## serverless.ymlの設定
設定した内容は以下です。（おそらく最低限の設定）
timeoutやmemorySizeは、デフォルトで書いたものが設定されるので適したもの指定する。

コードは、`handler.py`に記述して、`functions`の名前をよしなに変更する。（デフォルトは、helloという名前になっている）
```yaml
service: serverless-check-audrey

frameworkVersion: '3'

provider:
  name: aws
  runtime: python3.7
  timeout: 480 # default is 6
  memorySize: 256 # default is 1024
  region: ap-northeast-1

functions:
  check:
    handler: handler.check
    layers:
      - arn:aws:lambda:XXXXX:chromeのlayer
      - arn:aws:lambda:XXXXX:slackwebのlayer
      - arn:aws:lambda:XXXXX:seleniumのlayer
    events:
      - schedule: cron(0 11 * * ? *)

# plugins:
#   - serverless-python-requirements

```

## ハマりポイント
### seleniumがpython3.9で動かない
AmazonLinux2だと、一部のライブラリが動かない？（あいまいです）ようで、3.7にして解決

### scheduleのcronの書き方
Linuxのcron設定の引数の数が異なっていて、ずっと間違えていました。

ただ、eventsでscheduleを書くだけで、EventBridge (CloudWatch Events)が勝手に作成・紐付けされて、スゴイです。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/services-cloudwatchevents-expressions.html

## これから
layer部分は、前回でも使用していたものをそのまま使ってしまいましたが、ここもserverlessで管理することができるのでこちらもやる予定です。
Seleniumを利用してEdgeを自動化する(初心者備忘録)

## １．はじめに  
業務で毎月Webアプリのテストを実施しているのですが、  
その自動化を進めるにあたり、自身のPCでも自動化できないかと考えました。  
そこで、今回実際にやってみることにしました。  
インターネットで「Edge 自動化」で調べると「Selenium」「Python」を使用することで  
自動化できるようだったので、今回はその2つを使用して自動化を試してみました。  
  
------
## ２．環境  
今回は下記の環境で動かしてみました。  
・Microsoft Edge バージョン 102.0.1245.39  
　※Chromeでもできるようですが今回はEdgeで。  
・Edge WebDriver  
　※上記Edgeのバージョンと同じDriverをインストールする  
・Python 3.10.5  
・Selenium  
  
------
## ３．Seleniumとは  
SeleniumはWebアプリケーションをテストするためのフレームワークで、  
Webブラウザの操作・制御を自動化することができます。  

今回は２．で記載した「Edge WebDriver」と「Python」をインストールした後、  
コマンドプロンプト上で`pip install selenium`と入力すると、最新バージョンの  
Seleniumがインストールできます。  
  
------
## ４．実際にやってみた  
インストールが完了したら、任意のフォルダに.pyファイルを作成します。  
（今回は「test.py」という名前のファイルにしました）  
中身は以下のような記述にします。  
（今回はGoogleのサイトを自動的に表示させるまでを記述します）  

```  
from selenium import webdriver  
```  

①SeleniumからWebdriverをインポートします。  

```  
driver = webdriver.Edge(executable_path="C:\xxx\msedgedriver.exe")  
driver.get("http://google.com")  
```  

②Edgeを起動するためにwebdriver.Edge()を使用します。  
　引数には取得したmsedgedriver.exeへのパスを指定します。  
③Edgeを起動後、希望のサイトにアクセスするためにget()メソッドを使います。  
　引数にはURLを記載します。  

上記まで記載してファイルを保存したら、コマンドプロンプトを起動し、  
保存したファイルのある場所まで移動して、`python test.py`を入力します。  
  
------
## ５．最後に  
今回はサイトの画面を表示させるまでを実際に自動化してみましたが、  
基本的なブラウザ操作は自動化できるようなので、これからもいろいろ試したいと思います。 

------ 
## ６．参考文献
【Python】Selenium：Edgeを自動操作する方法  
https://office54.net/python/scraping/selenium-edge-control#section3  

【Python】send_keys・・・キーボード入力をする(特殊キー)  
https://www.seleniumqref.com/api/python/element_set/Python_special_send_keys.html
Docker create image and run with Kubernetes

### Docker
1. Create a test Nodes.js app.js
```js
const http = require('http');
const os = require('os');
console.log("Tellhost server starting...");
const handler = function(request, response) {
	console.log("Received request from " + request.connection.remoteAddress);
	response.writeHead(200);
	response.end("You've hit " + os.hostname() +" from " + request.connection.remoteAddress+ "\n");
};
const www = http.createServer(handler);
www.listen(8080);
```
1. create Dockerfile
```
FROM node:16
ADD app.js /app.js
ENTRYPOINT ["node", "app.js"]
```
1. Directory structure
```
--
 -- app.js
 -- Dockerfile
```
1. Create Docker Image
`docker build -t tellhost .` Run this command in the same directory with Dockerfile
1. Show all images
`docker images`
1. Run the image
`docker run --name tellhost-container -p 8080:8080 -d tellhost`
> -d: The container will be detached from the console (-d flag), which means it will
run in the background. 
-p 8080:8080 map 8080 on local machine to 8080 inside the container
1. show all running containers
`docker ps`
`docker ps -a` show all running and stopeed
1. Show the detail of one container
`docker inspect tellhost-container`
1. Run bash shell inside the container
`docker exec -it tellhost-container bash`
>-i, which makes sure STDIN is kept open. You need this for entering commands into the shell.
-t, which allocates a pseudo terminal (TTY).
1. Stop container
`docker stop tellhost-container`
1. Remove container
`docker remove tellhost-container`
### Kubernetes
1. show server status
`kubectl cluster-info`
1. show all nodes
`kubectl get nodes`
1. show detail of one node
`kubectl describe node your-node-name`
1. run one image
`kubectl run tellhost --image toka9988/tellhost --port=8080`
It doesn't map the port on local machine.So you need to login the container.
1. expose the pod
`kubectl expose pod tellhost --type=LoadBalancer --name tellhost-http`
1. deploy application
`kubectl create deployment tellhost2 --image=toka9988/tellhost`
1. expose deployment
`kubectl expose deployment tellhost2 --type=LoadBalancer --port 8080`
1. show all deployment
`kubectl get deployments`
1. show all pods
`kubectl get pods`
1. scale out
`kubectl scale deployment tellhost2 --replicas=3`
1. show pod's detail
`kubectl describe pod your-pod-name`
1. show services
`kubectl get services`
よくある「ホバーしたらドロップダウンメニューを表示するナビゲーション」を駆逐したい


## 正確には
「**ホバーした時だけ**ドロップダウンメニューを表示するナビゲーション」がどうかなぁ、と思っている次第です。
あと駆逐は言いすぎましたごめんなさい。「タイトルは弱く見えるくらい強い言葉を使え」って誰かが言ってたので（出典不明）

## どんなやつ？
こんなやつ。

<p class="codepen" data-height="500" data-default-tab="html,result" data-slug-hash="MWQZYqd" data-user="MentTori" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/MentTori/pen/MWQZYqd">
  Untitled</a> by MenTori (<a href="https://codepen.io/MentTori">@MentTori</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

カーソルでホバーするとドロップダウンでネストしたナビゲーションが表示されます。
多くの情報をスッキリとした見た目に収めることができる、とても素晴らしいUI👍

## なにがよくないの？
**タブキーでアクセスできない**ことです。。。この一点だけです😫
![画面収録1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1157551/349c0e72-41fb-8f1c-bf9e-69d0c11ca1e2.gif)



## タブでアクセスできないなら、タブでアクセスできるようにすればいいじゃない
ということでタブキーでドロップダウンメニュー（以降ドロップメニュー）にアクセスできるようにしよう！！

▼ ゴール
![goal.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1157551/f2df397a-4e3d-af1a-46a4-9557c0d90304.gif)


### `tabindex`でフォーカス可能にする
まずデモ実装はドロップメニューのトリガー（以降トリガー）が`div`要素で実装されており、そもそもタブキーではフォーカスがあたらないです。
そのため`tabindex`属性を指定してフォーカス可能な要素にしてあげましょう！
[tabindex属性 - HTML Standard 日本語訳](https://momdo.github.io/html/interaction.html#the-tabindex-attribute)

```html
<div class="link link-hover" tabindex="0">
	product
	<div class="tooltip">
	...
```

これでタブ移動でフォーカスが当たるようになりました🎉

### ドロップメニュー内にフォーカスが移動した後もドロップメニューを表示させる
現状トリガー要素の`:hover, :focus`擬似クラスにスタイルを定義していて、両者の場合にドロップメニューを表示させています。
```css
/* .link is トリガー */
.link:hover > .dropDown,
.link:focus > .dropDown {
	display: block;
}
```

このままだとドロップメニュー内にフォーカスが移動すると上記のスタイルが適用されずに、ドロップメニューが非表示となってしまいます。これでは、いけませんね。
![画面収録2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1157551/d1c5e1bf-3acf-4b4b-95a9-53acc5a8d049.gif)

ということで、「トリガーの子要素にフォーカスが当たっている場合に」という状況のスタイルを定義します。
**そこで登場するのが`:focus-within`擬似クラスです！**
[:focus-within - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/:focus-within)

こちらは
> その要素または子孫要素にフォーカスがある場合に一致します。

ということで、なんとも今回の状況にベストマッチしてくれている擬似クラスですね。
「その要素または」ということでトリガーにフォーカスがある場合にも適用されるので、**今回の場合`:focus`擬似クラスをこちらの擬似クラスに書き換えれば万事解決ということになります！**
```css
.link:hover > .dropDown,
.link:focus-within > .dropDown {
	display: block;
}
```
## 完成
以下が完成形のデモとなります！
タブでドロップメニューにアクセスできるよ！やったね！！👏

<p class="codepen" data-height="500" data-default-tab="html,result" data-slug-hash="QWQzwBZ" data-user="MentTori" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/MentTori/pen/QWQzwBZ">
  Accessible by tab key</a> by MenTori (<a href="https://codepen.io/MentTori">@MentTori</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
HTMLとCSSの学習ログ

# 学習教材
udemyで[「ウェブ開発入門完全攻略コース - HTML/CSS/JavaScript. プログラミングをはじめて学び創れる人へ！」](https://www.udemy.com/course/web-application-development/)を学習。
ウェブ開発全般の基本的な知識が習得できるコースで合計24.5時間もある。
今回はその中でもHTMLとCSSについて学習した。
[<img width=800 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2674841/6e448b63-dace-5900-335b-dfbb64d52531.png
">](https://www.udemy.com/course/web-application-development/)

# 学習範囲

## HTMLの基本
文章の基本構造や構文を演習を通じて学習した。

## HTMLのステップアップ
テーブルやお問い合わせフォームの作成を演習を通じて学習した。

## CSSの基本
マージン相殺やセレクタ、マージンやパディングを演習を通じて学習した。

## Flexboxの基本
6種類のプロパティを通じてFlexboxの利用方法を学習した。

## ポートフォリオサイトの作成
完成形をイメージしながら、簡単なポートフォリオを作成した。

## Bootstrap5
ナビゲーションやグリッドシステム、ジャンボトロンフォームを活用しながら簡単なフォトギャラリーを作成した。

# 学習を終えて
ウェブページの作成方法を理解することができて、興味を持って学習することができた。
また、Google Chromeのデベロッパーツールでソースコードを確認方法が分かり、非常に有意義な講義だった。
EC2でアセットファイルをコンパイルした際にエラーが出たときの対処法

以下の手順で、アセットファイルをコンパイルしたところエラーが出ました。
ターミナル（EC2内で実行）
```
$ rails assets:precompile RAILS_ENV=production
```

エラー文
```
yarn install v1.22.19
[1/5] Validating package.json...
error <リポジトリ名>@0.1.0: The engine "node" is incompatible with this module.Expected version "14.19.1". Got "14.19.3"
error Found incompatible module.
info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.
```
上記のエラー文の意味は、「Node.jsのバージョンが違うよ。バージョン"14.19.1"を使いたいのに"14.19.3"になってるよ。」です。

ですので、Node.jsのバージョンを"14.19.1"にする必要があります。

AWSのEC2上でNode.jsのバージョンをアップデートしたい場合は、nvmを使用します。

# nvmとは？

nvmとは、ノードのバージョン管理ソフトウェアで、Node Version Managerの略です。

複数のバージョンをインストールし、好きなバージョンに切り替えることもできます。npm(node package manager)も包括していて、バージョンを切り替えるとそれに合ったnpmが適用されます。

# nvmのインストール
GithubのREADME.mdをみてcurlコマンドでインストールします。

[nvm/README.md at master · nvm-sh/nvm · GitHub](https://github.com/nvm-sh/nvm/blob/master/README.md)
```
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
```
バージョンは日々更新されているようなので上記のリンクから最新版を入力した方がいいです。
# nvmの有効化
nvmをインストールしたら有効化（パスを通す）する必要があります。
```
$ . ~/.nvm/nvm.sh
```
# Node.jsをインストール
バージョンを"14.19.1"にしたいので"14.19.1"をインストールします。
```
$ nvm install 14.19.1
```
インストールされたバージョンを既存のバージョンに設定します。
```
$ nvm use 14.19.1
```
これでバージョンが"14.19.1"となり、再度アセットファイルをコンパイルしたところ処理が正常に進みました。
Preview画面において、適当な値をBinding変数に代入したい場合

<h1>Preview画面において、適当な値をBinding変数に代入したい場合</h1>

```swift
struct CustomInputField: View {
    let imageName : String
    let placeholderText :String
    //親Viewから状態変数を受け取る
    @Binding var text : String
    
    var body: some View {
        Text("Hello World!!")
    }
}

struct CustomInputField_Previews: PreviewProvider {
    static var previews: some View {
        //.constantとすることで適当な値をBinding変数に渡すことができる。
        CustomInputField(imageName: "enveloper", placeholderText: "Email", text: .constant(""))
    }
}
```
本日の勉強✏️

ドットインストール詳解CSS 基礎文法編

#21〜#33

CSSは奥が深い！
本当に細かくデザインを変えれるので楽しい。
動画と自分のパソコン上が一致してるのを見ると
嬉しくなる。笑


まだまだ初歩的なところだけど、
このまま続けていこうと思います。

コードを打つにあたって、正しいタイピングができるように矯正も必要だと感じましたので
Udemyと無料のタイピング練習サイトで練習しました。

それなりに早く打てるなら自己流でいいのでは、と思っていましたが、アルファベットの位置を頭に入れていた方がタイプミスが減る、そして第三者から見ても綺麗かなと思います。

自分のクセを直すのは大変ですが…
キーボード上で指があっちこっち踊ってるのは恥ずかしいので(*_*)



頑張ります！！
RspecでActiveRecord::RecordInvalid: バリデーションに失敗しました

#環境
---
初めて記事を書かせて頂きます🙇‍♂️
間違っていたら指摘お願いします
M1
ruby3.1.0
rails 6~

----

#経緯
----
一度通ったテストが
RspecでActiveRecord::RecordInvalid:バリデーションに失敗しました
に阻まれて沼ったお話

テスト対象はuserなのに何故か違うテーブルのバリデーションがかかってしまう

#結論
----
アソシエーションをしていたから

今回作っていたテーブルの関係性を説明します

userテーブル, postテーブル, categoryテーブル

user 1対多　post
user 1対多　category

post 多対1 user
post 多対1 category

category 多対1 user
category 1対多   post

アソシエーションを組む事によって
postを作るにはuser, categoryが必要
categoryを作るのはuserが必要
という風になっていました

#Rspec
----
```
spec/requests/posts_spec.rb


let!(:category) { create(:category) }
let!(:new_post) { create(:post) }
let!(:user) { create(:user) }

```
途中からアソシエーションを組んだのでこのコードがエラーになってしまっていた

順番を変えることによってエラーから抜け出せます
```
spec/requests/posts_spec.rb

#アソシエーションの関係上
#user→category→postで生成しなければならない

let!(:user) { create(:user) }
let!(:category) { create(:category) }
let!(:new_post) { create(:post) }

```

#終わり
---
同じエラーにはまっている方がこれで抜け出せればという思いで書かせて頂きました🧟‍♂️
[Ruby]warning: already initialized constant Digest::REQUIRE_MUTEXの解決法

## 概要
ある日自分の開発しているPFでこのような警告表示が出たので解決法を備忘録として残します。

rails routesを実行後
```
~省略/.rbenv/versions/3.0.2/lib/ruby/3.0.0/digest.rb:6: warning: already initialized constant Digest::REQUIRE_MUTEX
〜省略/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/digest-3.1.0/lib/digest.rb:20: warning: previous definition of REQUIRE_MUTEX was here
```

## 解決法
[こちら](https://geniusium.hatenablog.com/entry/2021/06/15/133317)の記事を参考に
概要gemをアンインストールした後、updateした
```
$ gem uninstall digest
```
```
$ gem update --default
```

## 結果
警告が消えました！
原因がいまだ不明なのですがご教授いただける方いれば幸いです。

## 参考
https://geniusium.hatenablog.com/entry/2021/06/15/133317
【Git】メモ

## なんかがみやすい
```sh
$ git log .
```

## wslもコンテナ
エミュレーション（ホストマシン上で異なるOSを仮想的に動かす）してる

## .gitフォルダ
objects,refs
→履歴管理をしている

## 選択行のみgit add 
stage selected ranges

https://qiita.com/eyuta/items/a26bcc327139e9fd81d4

## HEAD
今いるブランチの一番先頭

https://nebikatsu.com/6367.html/
brewでインストールするしたMySQLを止める方法 

mysqlのプロセスをkillしても、MySQLサービスが停止出来なかったので、どうすれば良いんだろうと調べた結果を書いておきます。

以下のコマンド実行でMySQLのサービスが止まりました。

```
$brew services stop mysql@5.6

Stopping `mysql@5.6`... (might take a while)
==> Successfully stopped `mysql@5.6` (label: homebrew.mxcl.mysql@5.6)
```

以下のコマンドでMySQLサービスを起動出来ます。
```
$ brew services start mysql@5.6
==> Successfully started `mysql@5.6` (label: homebrew.mxcl.mysql@5.6)
```
使用していたツールで起こった現象（閑話）

# はじめに
ツールを使用しているときに良く分からない現象が発生したので記事にしてみました。

# 使用機器
||バージョン|
|:-:|:-:|
|Visual Studio Code|1.67.2|
|Hex Editor|v1.9.6|
|Unity|2021.3.3f1|

# 現象1
HEXファイルからバイナリファイルを作成して、VS Codeの拡張機能であるHex Editorで変換結果を確認していたところ不思議な現象に遭遇しました。
アドレス 0x0019 FFF0 の表示が全て 0 と表示されていました。

全て 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F と書き込んでいるのになぜか全て 0 と表示されている。
![binfile.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148400/332b4b2d-2b0d-f220-36cd-192f87a8fd26.png)

別のツールで確認すると問題なく 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F が書き込まれていました。
![binfile2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148400/fa9149aa-a6fa-993e-ba59-2067aded0e2d.png)

テストプログラムから確認用のバイナリファイルを作りました。
```test.py
""" converts strings to numbers """
def _str2byte(hi, lo):
    byte = (int(hi, 16) * 16) + int(lo, 16)
    return byte


""" converts hex to bin """
def hex2bin():
    sample = '000102030405060708090A0B0C0D0E0F'

    # create binary file
    testbin = open('test.bin', 'wb')

    for _ in range(106500):
        it = iter(sample)

        for hi, lo in zip(it, it):
            conv = _str2byte(hi, lo)
            temp = conv.to_bytes(1, 'little')
            # write data to binary file
            testbin.write(temp)

    # flush
    testbin.flush()

    # close binary file
    testbin.close()


hex2bin()
```

# 現象2
Unityを使用しているときに、Imageにスプライトをドラッグ＆ドロップしていたら操作を誤ったようでシーンビューにスプライトがドロップされていました。
シーンビューに配置したものはヒエラルキービューに表示されるはずですが、ヒエラルキービューにも表示されることなく間違って配置したスプライトを削除することができなくなりました。

本来であればシーンビューに配置したものはヒエラルキービューに配置されるはずが配置されない。
![unity.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148400/178141b8-98aa-57d1-481b-aba85f859d59.png)

スプライトを削除したかったのでシーンファイル（*.unity）を直接編集してスプライトを削除しました。
![scene.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148400/6b1e9de0-52ec-0277-e713-ac4c4a88aa44.png)

# 編集後記
いろいろなことが面白いですね。
SparkのSelectとSelect-exprのディープダイブ

[Spark Select and Select\-expr Deep Dive \| by somanath sankaran \| Analytics Vidhya \| Medium](https://medium.com/analytics-vidhya/spark-select-and-select-expr-deep-dive-d63ef5e04c87)の翻訳です。

:::note warn
本書は抄訳であり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
:::

この記事はcsvの読み込みに対するディープダイブの後の**sparkディープダイブシリーズ**の3番目の記事です。

https://medium.com/analytics-vidhya/spark-csv-deep-dive-part-ii-6d3389904f71

この記事はpysparkにおけるselectとfilterの表現の取り扱いについて言及しています。

1. Selectとエイリアスのカラム
1. フレキシブルなSelectExpr(Hiveの人たち向け)
1. Pythonの力(リストの内包表現)とselectの活用

# ステップ1: 入力データフレームの作成

Sparkセッションのread csvメソッドを使ってデータフレームを作成します。
![](https://miro.medium.com/max/1400/1*GaoNT1Uq3Dc4EjwWLD9TBg.png)

# ステップ2: DFにおけるSelect

ドキュメントによると、df.selectは以下を受け付けます。

1. 文字列のリスト
1. Columnのリスト
1. エクスプレッション
1. "*"(スター)

# 1. 文字列のリスト

Pythonの文字列オブジェクトのリストとしてカラム名を渡すことができます。

![](https://miro.medium.com/max/830/1*9jx3cRqDORZ5B644W2Avyw.png)

# 2. Columnのリスト

pyspark.sql.functionsのspark Columnクラスをインポートし、Columnのリストを渡すことができます。
![](https://miro.medium.com/max/1100/1*gWjaAhMw344FxT61b1iflA.png)

# 4. スター("*")

スター構文は基本的にsqlの`select *`と同じようにすべてのカラムを選択します。
![](https://miro.medium.com/max/1400/1*fQ-I5U1iZpSR-L6UTTywIQ.png)

# ステップ2: エイリアスによるSelect

一般的なユースケースの一つは、何かしらのデータ操作を行い、データをshowではなく新規データフレームとして割り当てるというものです。

例えば、Fairを70倍してUSドルからインドルピーに変換し、カラム名をFare_INDにするものとします。
![](https://miro.medium.com/max/1400/1*18ceY4mgWDKxVWo2Ig0vEA.png)

ここで、全ての列を選択し、新たな列Indian_INRを追加します。

# フレキシブルなSelectExprとエイリアスのカラム

もしあなたが私のようにsql/Hiveユーザーであ理、あるいはsparkのcase構文を思い出せないとしたらどうしましょうか。

心配しないで下さい。selectExprがあなたを助けてくれます。

1. SelectExprはフレキシブルなsql文やフィールドの追加に役立ちます。
![](https://miro.medium.com/max/1400/1*469pBDQJXsc0KVSPxnO5Xg.png)

1. lengthのようなビルトインのHive関数を利用できます。
![](https://miro.medium.com/max/1170/1*zgOb1bqQesamNIvj3A9HDw.png)

1. selectExprを用いることでデータ型のキャストも簡単です。
![](https://miro.medium.com/max/1298/1*ICcqcfxMDOgGss6FN5SECA.png)

1. SelectExprを用いて定数を追加します。

一般的なユースケースの一つは、`current_date`のような定数フィールドを追加するというものです。

SelectExprを用いることで簡単に達成できます。
![](https://miro.medium.com/max/1400/1*lgqNlhJhCZQzkScBnnuQkg.png)

# 3. Pythonの力(リストの内包表現)とselectの活用

selectはListを受け付けるので、特定のカラムを選択するのにリストの内包表現を使うことがd系ます。データフレームを操作するためにColumnのリストがユーザーに与えられているとします。

必要なカラムを選択すルタめにif句を用いたリストの内包表現(あるいはジェネレーター表現)を利用することができます。
![](https://miro.medium.com/max/1266/1*G_hN9FxhJDUE_R2k9R9q-w.png)

Githubリンク:
[https://github\.com/SomanathSankaran/spark\_medium/tree/master/spark\_csv](https://github.com/SomanathSankaran/spark_medium/tree/master/spark_csv)

[Next Tutorial : https://medium\.com/@somanathsankaran/spark\-group\-by\-and\-filter\-deep\-dive\-5326088dec80](https://medium.com/@somanathsankaran/spark-group-by-and-filter-deep-dive-5326088dec80)

学んで、他の人も学ぶようにしましょう！！
kerasでデータの拡張をする

どうもエンジニアのirohasです。

先日、[kerasを使用して虹ヶ咲学園のキャラクターを画像認識させてみた](https://qiita.com/irohas_gawr/items/c13a59e59d1cde18e5be)の記事において、データ数の少なさから精度があまり芳しくなく、データを増やそうと思い、Data Augmentation(データ拡張)をしたので、
今回は機械学習ライブラリの一つであるkerasを用いた、Data Augmentation(データ拡張)を紹介します。

# 目次
[1.環境](#1-環境)
[2.使用ライブラリ](#2-使用ライブラリ)
[3.データ拡張って何？](#3-データ拡張って何？)
[4.ソースコード](#4-ソースコード)
[5.まとめ](#5-まとめ)


# 1. 環境
vscode
Python 3.9.0

# 2. 使用ライブラリ
##### ・標準ライブラリ
os : ローカルフォルダ用ライブラリ
glob :　フォルダ一括取得用ライブラリ

##### ・外部ライブラリ
numpy 1.21.4 : 数値計算ライブラリ
matplotlib 3.5.1 : グラフ描写用ライブラリ
Keras 2.8.0 : ディープラーニング用ライブラリ

# 3. データ拡張って何?
Data Augmentation（データ拡張）とは、学習用の画像データに対して「変換」を施すことでデータを水増しする手法です。
機械学習、特にディープラーニングによる画像認識では大量の画像データを必要とする場合が多いです。
時にその量は数万枚や数十万枚（またはそれ以上）に登る場合もあるので、人間の手でデータを集めたり、スクレイピングで集めるのも至難の業となってきます。
そこで活躍するのが上記のData Augmentation（データ拡張）という技術です。
Data Augmentation（データ拡張）では、一枚の画像を回転させたり、反転させたり、明るさを調整したりしてデータを増やすことができます。

詳しくはこちら↓
[Keras ImageDataGenerator](https://keras.io/ja/preprocessing/image/)

# 4. ソースコード

作成したソースコードは以下になります。

```python: aug.py
from keras.preprocessing.image import load_img, img_to_array
from keras.preprocessing.image import ImageDataGenerator
import matplotlib.pyplot as plt
import numpy as np
import os
import glob

n = 20 #一枚あたりの拡張数

# 入力画像の保存先
input_path = "./dataset/train/sizuku"
files = glob.glob(input_path + '/*.jpg')

# 出力画像の保存先
output_path = "./dataset/train/sizuku"
if os.path.isdir(output_path) == False:
    os.mkdir(output_path)

for i, f in enumerate(files):

  img = load_img(f)
  x = img_to_array(img)
  x = np.expand_dims(x, axis=0)

  # ImageDataGeneratorの生成
  datagen = ImageDataGenerator(
    zca_epsilon=1e-06,   # 白色化のイプシロン
    rotation_range=10.0, # ランダムに回転させる範囲
    width_shift_range=0.0, # ランダムに幅をシフトさせる範囲
    height_shift_range=0.0, # ランダムに高さをシフトさせる範囲
    brightness_range=None, # ランダムに明るさを変化させる範囲
    zoom_range=0.0,        # ランダムにズームさせる範囲
    horizontal_flip=True, # ランダムに水平方向に反転させる
    vertical_flip=True, # ランダムに垂直方向に反転させる
  )

  # 1枚あたりn枚の画像を水増し生成
  dg = datagen.flow(x, batch_size=1, save_to_dir=output_path, save_prefix='img', save_format='jpg')
  for i in range(n):
    batch = dg.next()
```
# 5. まとめ
Data Augmentation（データ拡張）を使えば、100枚のデータを1000枚、10000枚などまで増やすことができます！
Data Augmentation（データ拡張）をすることで、過学習の抑制であったり、精度の向上につながります。
ぜひ、Data Augmentation（データ拡張）の技術も極めて快適なAI開発ライフをお過ごしください！！
【Vue.js】v-forで:keyの指定がなぜ必要なのか


## ぶっちゃけ
keyを指定せずにv-for を使おうとするとエラーになるので、あまり深く理解せずおまじないだと思っていても問題ありません。

## 結論
効率的に描画(レンダリング)をするため。

## 詳細
配列にはインデックスという「どの要素が配列の何番目にあるのかを表すための数字」があるので、このインデックスがあれば`:key`を指定する必要が無いのでは?と思うかもしれません。
しかし実際はこのインデックスだけだと、描画するときにかなり非効率になってしまいます。

`v-for`は配列の内容を動的に描画するための仕組みなので、配列を並べ替えたり、値を変えたりしてもちゃんと描画をしてくれます。インデックスだけの場合、配列の変更が一部であっても、配列の全要素を再描画することになるためかなり非効率な処理となってしまいます。

そのためインデックスとは別に、`:key`に各要素を一意に表す数字を指定することで、配列の一部変更があったら、その箇所だけを再描画してくれる様になります。
余談ですが、`:key`は全てのコンポーネントで最初から定義されているため自分で定義する必要はありません。

```vuejs:例
<ul>
  <li v-for="item in items" :key="item.id">
    {{ item.message }}
  </li>
</ul>
```
【Git】コマンドの基本操作

# はじめに
ターミナル上でGitを使用ためのコマンド入力方法をおさらいしていきます。

## clone
GitHub(リモートリポジトリ)からソースコードをダウンロードします。
ダウンロードに使用するURLはGitHub上の`Code`というボタンを押すと表示されます。
```
$ git clone <リモートリポジトリのURL>
```
## init
ローカルのソースコードをGitで管理するときの最初に入力するコマンドです。
このコマンドを入力することでソースコードをGitの管理下にすることができます。
```
$ git init
```

## add
ファイルをインデックス(ステージング)に追加します。
※指定するファイル名の箇所はディレクトリ名などでも可能です。
　.(ドット)を使用すると階層配下の全ファイルを指定できます。
```
$ git add <ファイル名>
```

## commit
ファイルをコミットします。
```
$ git commit <ファイル名>
```

## status
現在のファイルの状況を確認します。
```
$ git status 
```

## diff
現在のワークツリーとHEADの差分を表示します。
```
$ git diff 
```

## log 
コミットの履歴を確認できます。
オプションで`--oneline`や`-p`、`-n`などをよく指定します。
`--oneline`は各コミットを1行で表示します。
`-p`はコミットごとにファイルの変更差分を表示できます。
`-n`は履歴の表示を直近のn個前のみ表示するオプションです。nには数字が入ります。
```
$ git log
```

## checkout
ブランチの切り替えに使用します。
```
$ git checkout <ブランチ名>
```

## stash 
ソースの一時保存を行います。
`stash list`でstashの一覧を表示し、`stash apply`で一時保存したものを取り出せます。
```
$ git stash save <"stashの名前"> 
```
```
$ git stash list
```
```
$ git stash apply stash@{<stash listで表示した番号>}
```

## reset 
変更を取り消します。
オプションは`--soft`、`--mixed`、`--hard`があり、デフォルトは`--mixed`です。
```
$ git reset <コミットのハッシュ値>
```

## branch
ブランチの作成を行います。
ブランチ名の指定がない場合は、存在するブランチ名を表示します。
```
$ git branch <ブランチ名>
```

## remote
リモートリポジトリの登録
`リモート名`には`origin`などがよく使用されます。
```
$ git remote add <リモート名> <リモートリポジトリのURL> 
```

## push 
ローカルリポジトリの内容をリモートリポジトリに反映します。
```
$ git push <リモート名> <ブランチ名>
```


## merge
他ブランチのコミット履歴を統合します。
```
$ git merge <ブランチ名>
```

## fetch
リモートリポジトリの内容を取得します。
(リモート追跡ブランチに取得)
```
$ git fetch <リモート名> <ブランチ名>
```


## pull
pullは`git fetch` と`git merge`を同時に行います。
そのため、コンフリクトに注意が必要です。
```
$ git pull <リモート名> <ブランチ名>
```




## 参考
[いまさらだけどGitを基本から分かりやすくまとめてみた](https://qiita.com/gold-kou/items/7f6a3b46e2781b0dd4a0)
M5StackにCAT-Mモジュールをつないでhttpsで通信する

## 用意するもの
- M5Stack
- CAT-Mモジュール
- SIMカード
- 通信先
## 使うライブラリ
- TinyGSM
## コード例
証明書だなんだかんだを用意しなくていいので楽。
ambient.ioにjsonを送ります。

```c
#include <ArduinoJson.h>
#include <ArduinoHttpClient.h>
#define TINY_GSM_MODEM_SIM7080

#define APN "soracom.ioとか"
#define USER "SIMカードによる"
#define PASS "SIMカードによる"

const char server[] = "ambidata.io";
const char channelId[] = "***";
const char writeKey[] = "***";
char resource[1024];
int port = 443;

#include <TinyGsmClient.h>
TinyGsm modem(Serial1);
TinyGsmClientSecure client(modem);

HttpClient http(client, server, port);
int statusCode = 0;

const int capacity = JSON_OBJECT_SIZE(5);
StaticJsonDocument<capacity> doc;
char payload[2048];
Serial1.begin(115200, SERIAL_8N1, 32, 26); //3番目と4番目の引数はCAT-Mモジュールとの通信で使うポートのRXとTX
modem.restart();

sprintf(resource, "/api/v2/channels/%s/data", channelId);

while (!modem.waitForNetwork() && !modem.isNetworkConnected())
{
  delay(1);
}
SerialBT.printf("APN: %s\n", APN);

modem.gprsConnect(APN, USER, PASS);
while (!modem.isGprsConnected())
{
 delay(1);
}
Serial.printf("Operator: %s\n", modem.getOperator().c_str());

http.connectionKeepAlive(); // ここがポイントらしい
int err = http.post(resource, "application/json", payload);
if (err != 0)
{
  Serial.println("Failed to Connect");
}
statusCode = http.responseStatusCode();
if (statusCode == 200)
{
  Serial.println("Success!");
}
else
{
  Serial.printf("Failed, StatusCode: %d\n", statusCode);
}
Serial.println("Disconnect from Network");
http.stop();
client.stop();
modem.gprsDisconnect();
modem.poweroff();
```
接続を生かしておくか、モデムの電源まで切るかはお好みで。
Dart & Flutter を使ってアプリを作成してみた

# 成果物（家計簿アプリ）
https://play.google.com/store/apps/details?id=mone.takeo.flower

https://apps.apple.com/app/id1628290995

# 参考にしたもの
- [Dart](https://dart.dev/)

https://dart.dev/

- [Flutter](https://flutter.dev/)

https://flutter.dev/

- [Flutter JP](https://flutter.ctrnost.com/)

https://flutter.ctrnost.com/

- アイコン

https://fonts.google.com/icons?selected=Material+Icons&icon.query=+&icon.style=Outlined

- デザイン（主に色）

https://api.flutter.dev/flutter/material/Colors-class.html

- DB

https://pub.dev/packages/sqflite

- アプリのアイコンなど

https://zenn.dev/susatthi/articles/20220401-060335-flutter-launcher-icons

- グラフ

https://pub.dev/packages/charts_flutter


# React Nativeと比較しての個人的感想
- iOSとAndroidの両方対応するのはDartの方が楽な気がした（開発環境なども含めて）
- ドキュメントなどの豊富さはあまり変わらない
- flutterの方が**sqlite**との連携が楽だった気がした


以上
nginxでルートドメインにリクエストが来たときだけ外部にリダイレクトしたい

あまりないと思いますが……
サブドメインでも同じような設定をすると応用できそうです。知らんけど。
## 状況
- `sub.example.com`にアクセスされたらそのまま`sub.example.com`を開く
- `example.com`にアクセスされたら`example.jp`にリダイレクトする

## 設定
ルートドメインに関係する設定ファイルに`location ~ ^/$`でよしなに。
```config
server {
     listen 443 ssl http2;
     listen [::]:443 ssl http2;
 
     server_name example.com;
     location ~ ^/$ {
              return 301 https://example.jp/;
     }
}
```

## 参考資料
- [How to redirect only the root path in nginx? - Stack Overflow](https://stackoverflow.com/questions/41755100/how-to-redirect-only-the-root-path-in-nginx)
【個人開発】Streamlitで日経225株価可視化アプリを作成してみた

<h2>【はじめに】</h2>
ここ最近Pythonに慣れてきたので、Webフレームワークの「Streamlit」を使って簡単なWebアプリを作ってみたので記事にまとめました。
実際に作成した、日経225銘柄の株価可視化アプリのURLを貼っておきます。

https://share.streamlit.io/nakachan-ing/finance-streamlit/app.py
<h3>本記事の内容</h3>
<ul>
 	<li>作成背景</li>
 	<li>使用技術</li>
 	<li>画面説明</li>
 	<li>実際に作成してみた感想</li>
</ul>
<h2>【作成背景】</h2>
最近、副業が解禁になりましたが始める人も多いと思います。
ですが、まだ副業を禁止している会社も多いです。
そんな中で、副業ではなく貯蓄を増やすためにできることは株投資だと思います。
投資をこれから始めようと思っている人もしくは既に投資をしている人のために、手軽に銘柄と株価を確認できるアプリがあればと思い作成しました。
<h2>【使用技術】</h2>
Streamlitを用いることでWeb開発で通常必要なHTMLやCSSでのフロントエンドの記述が不要で、Pythonスクリプトのみで良い感じのアプリを開発することが可能です。
また、Streamlit SharingというStreamlitで作成したアプリを無償でデプロイできるサービスを利用することで簡単にデプロイができます。
<h3>バックエンド</h3>
<ul>
 	<li>Python、Streamlit</li>
</ul>
<a>https://docs.streamlit.io/</a>
<h3>コード管理</h3>
<ul>
 	<li>GitHub</li>
</ul>
<a>https://github.com/nakachan-ing/finance-streamlit</a>
<h3>デプロイ</h3>
<ul>
 	<li>Streamlit Sharing</li>
</ul>
<a>https://share.streamlit.io/</a>
<h2>機能説明</h2>
全体画面は次のようになっています。
<a href="https://gyazo.com/c6eb07893b2475ed5a288a249393a8d2"><img src="https://i.gyazo.com/c6eb07893b2475ed5a288a249393a8d2.png" alt="Image from Gyazo" /></a>
<h3>メイン画面</h3>
<ul>
 	<li>日経225の銘柄を選択します。複数選択できるようになっており、デフォルトは上位3銘柄を表示しています。</li>
</ul>
<a href="https://gyazo.com/39fb2fc7a856f23a7c2d161a5782db18"><img src="https://i.gyazo.com/39fb2fc7a856f23a7c2d161a5782db18.png" alt="Image from Gyazo" /></a>
<ul>
 	<li>選択した銘柄に対して、株価を表形式とグラフ形式で可視化します。</li>
</ul>
<a href="https://gyazo.com/abc75800905c3e6dd0e94be365465940"><img src="https://i.gyazo.com/abc75800905c3e6dd0e94be365465940.png" alt="Image from Gyazo" /></a>
<ul>
 	<li>表示されているグラフは、次の形式でダウンロードすることができます。</li>
</ul>
<a href="https://gyazo.com/70611e935b339687d402ec7ec6183e69"><img src="https://i.gyazo.com/70611e935b339687d402ec7ec6183e69.png" alt="Image from Gyazo" /></a>
<h3>サイドバー画面</h3>
<ul>
 	<li>画面左側はサイドバーになっていて、「表示させる日数」と「株価の範囲」を指定できます。デフォルトは直近25日間、0円〜10000円の範囲で表示されています。</li>
</ul>
<a href="https://gyazo.com/3352d637bc0c3c81cf0c13f4d6486e73"><img src="https://i.gyazo.com/3352d637bc0c3c81cf0c13f4d6486e73.png" alt="Image from Gyazo" /></a>
<ul>
 	<li>株価の範囲を指定することで、銘柄の株価の遷移をより詳細に可視化することができます。</li>
</ul>
<a href="https://gyazo.com/e47630476f76d2bcac06937b4d7f0917"><img src="https://i.gyazo.com/e47630476f76d2bcac06937b4d7f0917.png" alt="Image from Gyazo" /></a>
<h2>【実際に作成して見た感想】</h2>
今回、Streamlitを使うことで簡易にアプリ作成ができることを実感しました。
もっと、見た目にこだわったり多機能なアプリを作ろうと思ったらやはり、FlaskやDjangoを使う方が良いと思います。

Streamlitは次のようなケースに特に威力を発揮します。
<ul>
 	<li>社内での分析資料を可視化して見せたい。</li>
 	<li>Webシステムを構築して社内で共有したいが、DjangoやFlaskのように大掛かりで作るほどではない。</li>
 	<li>可視化された資料を、誰でも見れて作れるようなシステムを構築したい。</li>
</ul>
<h2>【おわりに】</h2>
Streamlitは、Pythonで分析や処理したデータを簡単にプレゼンや共有するためにかなり強力なフレームワークです。
Python初学者でも学習しやすいフレームワークだと思うので、是非試してみてください。
テスト70

```vb

'Excelファイルを開かずにシートのスペース削除と指定の値を検索する方法
Sub ExcelADO3()
'DAOを使ってExcelファイルを操作する方法も試してみたが、速度的にはADOの方が速かった
'DAOはAccessに特化したDBエンジンなのでAccessで使えば非常に速くなると思われる
'また、Excelファイルを開いておかないとDAOは使えない
'そのため、閉じた状態のExcelを操作するにはADOの方が良い....〆(･ω･ )ﾒﾓﾒﾓ

Dim cn As New ADODB.Connection
Dim rs As New ADODB.Recordset
Dim strSQL As String
Dim str As String
Dim i As Long
Dim fil_str As String

i = 1
Application.ScreenUpdating = False

str = "C:\Users\User\Desktop\VBA練習\test.xlsx"

cn.Open ("Provider=Microsoft.ACE.OLEDB.12.0;" & _
         "Data Source=" & str & ";" & _
         "Extended Properties=""Excel 12.0;HDR=YES""")

strSQL = "SELECT * " & _
         "From [テスト1$A1:J20000];"

With rs
    .Source = strSQL
    .ActiveConnection = cn
    .LockType = adOpenDynamic
    .Open
End With


rs.MoveFirst
Do Until rs.EOF

    rs.Fields(0).Value = Replace(StrConv(rs.Fields(0).Value, 8), " ", "")
    rs.Fields(1).Value = Replace(StrConv(rs.Fields(1).Value, 8), " ", "")
    rs.Fields(2).Value = Replace(StrConv(rs.Fields(2).Value, 8), " ", "")
    rs.Fields(3).Value = Replace(StrConv(rs.Fields(3).Value, 8), " ", "")
    rs.Fields(4).Value = Replace(StrConv(rs.Fields(4).Value, 8), " ", "")
    rs.Fields(5).Value = Replace(StrConv(rs.Fields(5).Value, 8), " ", "")
    rs.Fields(6).Value = Replace(StrConv(rs.Fields(6).Value, 8), " ", "")
    rs.Fields(7).Value = Replace(StrConv(rs.Fields(7).Value, 8), " ", "")
    rs.Fields(8).Value = Replace(StrConv(rs.Fields(8).Value, 8), " ", "")
    rs.Fields(9).Value = Replace(StrConv(rs.Fields(9).Value, 8), " ", "")

    rs.MoveNext

Loop
rs.MovePrevious
rs.Update

'----------------------
'一度閉じる
rs.Close
Set rs = Nothing

cn.Close
Set cn = Nothing

'----------------------
rs.CursorLocation = adUseClient

'再度接続
cn.Open ("Provider=Microsoft.ACE.OLEDB.12.0;" & _
         "Data Source=" & str & ";" & _
         "Extended Properties=""Excel 12.0;HDR=YES""")

With rs
    .Source = strSQL
    .ActiveConnection = cn
    .LockType = adOpenDynamic
    .Open
End With


With ThisWorkbook.Sheets("貼り付け")

    Do Until Trim(.Cells(i, "M").Value = "")
    
        fil_str = "あ = '" & Trim(.Cells(i, "M").Value) & "'"
        rs.Filter = fil_str
        
        If rs.RecordCount = 0 Then
        
'            MsgBox "該当するレコードは見つかりません"
'            End
            
        ElseIf rs.RecordCount >= 2 Then
        
'            MsgBox "該当するレコードが2件以上あります。"
'            End
           
        End If
    
        i = i + 1
    
    Loop

End With

'2万行10列の処理と5件の検索で約3～4秒...φ('ω' )ｶｷｶｷ
Stop

Application.ScreenUpdating = True

rs.Close
Set rs = Nothing

cn.Close
Set cn = Nothing
    
End Sub
WindowsでLinuxコマンドを実行する

WSL2をセットアップしたときの自分用メモ

---

# インストール
- Ubuntuをインストール
```powershell
PS C:\Windows\system32> wsl --list --online
インストールできる有効なディストリビューションの一覧を次に示します。
既定の分布は ' * ' で表されます。
 'wsl --install -d <Distro>'を使用してインストールします。

  NAME            FRIENDLY NAME
* Ubuntu          Ubuntu
  Debian          Debian GNU/Linux
  kali-linux      Kali Linux Rolling
  openSUSE-42     openSUSE Leap 42
  SLES-12         SUSE Linux Enterprise Server v12
  Ubuntu-16.04    Ubuntu 16.04 LTS
  Ubuntu-18.04    Ubuntu 18.04 LTS
  Ubuntu-20.04    Ubuntu 20.04 LTS
PS C:\Windows\system32> wsl --install -d Ubuntu
インストール中: 仮想マシン プラットフォーム
仮想マシン プラットフォーム はインストールされました。
インストール中: Linux 用 Windows サブシステム
Linux 用 Windows サブシステム はインストールされました。
ダウンロード中: WSL カーネル
インストール中: WSL カーネル
WSL カーネル はインストールされました。
ダウンロード中: Ubuntu
要求された操作は正常に終了しました。変更を有効にするには、システムを再起動する必要があります。
PS C:\Windows\system32>
```

## Ubuntuを起動するがエラー
- 仮想マシン内（WIndows）で仮想環境（Ubuntu）を起動していることが原因
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/afd83907-c6c1-1482-0016-ca0df513d80a.png)

## 解決策
- 物理マシンにて「インテル® プロセッサ 識別 ユーティリティ」をインストール  
  VT-x（最下部の項目）が有効であることを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/8cfa9c97-ba2b-c2f2-cb18-2ca84b38878c.png)

- VMware Workstation 16 Playerにて「Intel VT-x/EPT または AMD-V/RVI を仮想化」を有効化
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/41ef1f81-ae2b-a53f-6294-7533b483ceac.png)


# 設定
- ユーザーとパスワードを設定　※ここでは「user001」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/95cb8f50-4bde-f86a-b4fa-fa66192d5268.png)

- パッケージ更新
```
sudo apt update
sudo apt -y upgrade
```

- 日本語化
```
sudo apt -y install language-pack-ja
locale
sudo update-locale LANG=ja_JP.UTF8
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/d7f390dc-8487-517d-7dda-0d8d4cf7dc5a.png)

- 「exit」して再度起動して反映されていることを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/4588b0cd-50d8-1733-8004-967b8ef23783.png)

# ファイル移動
- エクスプローラーから「\\wsl$」を開く
- ドラッグアンドドロップでファイルを移動
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/eec1acef-605c-ff8b-d7a4-e248d3691990.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/73075785-eb9f-7dda-8b27-2713c20a050e.png)



# 感想
- 今までPowershellで似たようなコマンドを探したり、Git Bashを使っていた
    - バッククオートが使えなかったりと不便だった
- Windowsでも開発しやすくなった
- Cloud9を使うほどでもないときやローカル環境必須の場合に使っていきたい