Hannya64

本記事はクソアプリ2 Advent Calendar 2020の24日目の記事です。

Hannya64

https://inabajunmr.github.io/Hannya64/

デモ

f:id:inabajunmr:20201201235121g:plain

Base64の実装

注意

  • 筆者は普段こういうコードを全く書かないので、なんか間違ってたら教えて下さい
  • 効率とかは全然意識してないです
  • パディングとかURLエンコードの話とかはないです

基本的な方針

バイト列を先頭から3バイト取り出し、6ビット4つに変換する。6ビット(0〜63)を、文字列にマッピングしていく。

こちらを参考にさせてもらいました。

Algorithm Implementation/Miscellaneous/Base64 - Wikibooks, open books for an open world

実装

エンコード前のバイト配列を以下とする。

10000000 11000000 11100000 11110000 11111000 11111100

3バイトずつ処理する。最初は先頭の3バイトを処理する。

10000000 11000000 11100000

3バイトをくっつけて24ビットにする。 まず1バイト目を16ビット左シフトする。

10000000 << 16

結果右に0が16個くっついて以下になる。

        <---------------
100000000000000000000000

2バイト目を8ビット左シフトする。

                <-------
000000001100000000000000

3バイト目はそのまま。

000000000000000011100000

1つ目と2つ目をORでつなぐ。

100000000000000000000000 |
000000001100000000000000

ビット演算子のORは、いずれかが1だったら1になるので、以下のようになる。

100000001100000000000000

つないだ結果と3つ目もORでつなぐ。

100000001100000000000000 |
000000000000000011100000

結果、24ビットの列ができる。

100000001100000011100000

24ビットの列を18ビット右シフトして6ビット取り出す。

100000001100000011100000 >> 18

右シフトすると、左から0が詰められて右側のビットはあふれて消えるので、以下のようになる。

----------------->
000000000000000000100000

24ビットの列を12ビット右シフトして次の6ビットを取り出す。

----------->
000000000000100000001100

001100 だけを取り出したいので、111111 とANDで右から6桁だけ取り出す。

000000000000100000001100 &
000000000000000000111111

ANDは両方とも1だった時だけ1になるので以下になる。

000000000000000000001100

同じようにして次の6ビットも取り出す。

100000001100000011100000 >> 6 & 000000000000000000111111

最後の6ビットも同様に取り出す。最初の24ビットのうち、最後の6ビットを取り出せばよいので右シフトは不要となる。

100000001100000011100000 & 000000000000000000111111

この結果、この3バイトが

10000000 11000000 11100000

6ビット4つに変換できる。

100000 001100 000011 100000

6ビットなので、ここで得たそれぞれの値は000000(0)から111111(63)の64パターンになる。 10進数に変換すると以下になる。

32 12 3 32

以下のような文字列を用意する。

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

A が0で、/ が63として文字列のインデックスと数値の対応を変換すると以下になる。

gMDg

残った3バイトも同じ方法で変換していくと以下になる。

gMDg8Pj8

コード

Hannya64/hannya64.js at main · inabajunmr/Hannya64 · GitHub

なぜ共通化のために継承を使うべきではないかをできるだけ簡単に説明したい4J

多重継承できない言語でなるべく誰でもわかるように共通化のために継承を使うべきではない理由をまとめた。 継承を使うべきでない理由は他にもあるがとりあえず一番簡単に納得できそうな理由について記載した。 多重継承できたとしても、共通化のために継承を使うのは個人的にあまり良くないと思っているが、それについては記載しない。

前提

ある程度の規模感があるJavaのコードは責務ごとにクラスを分割するとコードが読みやすくなる(≒全部mainメソッドに書いたコードは読みづらい)。

そもそもコンストラクタでバリデーションしろよ、とかは無視してもとりあえず言いたいことは伝わる気がするので諦めた。

多重継承できる言語だとこの論理では継承よりもコンポジションを主張できない。 リスコフの置換原則の話とかはしない。

まとめ

Javaで継承による共通化を行うと、多重継承ができないため1つの親クラスに全ての共通処理を入れる必要がある。 つまり処理ごとにクラスを分割することができないし、クラスに適切な名前をつけることもできない(なので多くの場合Baseなんとかクラスになってしまう)。

これを避けるために無理やりクラスを分割すると、全く関連の無いクラス同士を親子関係にする必要が出てくる。 こうなると、あるクラスに手を入れる際に関係ないクラスを破壊していないか意識する必要が出てきて辛い。 関係ないクラス同士に親子関係があると、あとから見たときに混乱するので辛い。

以下のようなコードがあったとする。

public class UserCreator {
    public User createUser(User user) {
        if (user.id != null) {
            throw new IllegalArgumentException("ユーザーIDは必須です。");
        }
        if (user.age < 0) {
            throw new IllegalArgumentException("年齢は0以上である必要があります。");
        }

        // 永続化
        return db.create(user);
    }
}

public class UserUpdater {
    public User updateUser(User user) {
        if (user.id != null) {
            throw new IllegalArgumentException("ユーザーIDは必須です。");
        }
        if (user.age < 0) {
            throw new IllegalArgumentException("年齢は0以上である必要があります。");
        }

        // 永続化
        return db.update(user);
    }    
}

ユーザーを永続化する際にバリデーションを行っているが、このバリデーションはCreaterとUpdaterで重複している。 これを継承で共通化すると以下のようになる。

public abstract class UserPersistentBase {
    public void validateUser(User user) {
        if (user.id != null) {
            throw new IllegalArgumentException("ユーザーIDは必須です。");
        }
        if (user.age < 0) {
            throw new IllegalArgumentException("年齢は0以上である必要があります。");
        }
    }
}

public class UserCreator extends UserPersistentBase {
    public User createUser(User user) {
        validateUser(user);
        return repository.create(user);
    }
}

public class UserUpdater extends UserPersistentBase {
    public User updateUser(User user) {
        validateUser(user);
        return repository.update(user);
    }
}

validateUserがUserPersistentBaseに共通化され、UserCreator、UserUpdaterがすっきりした。 次にユーザーが作成されたり更新した際に、WebhookでHTTPリクエストが飛ぶ、という要件が追加されたとする。

public abstract class UserPersistentBase {
    public void validateUser(User user) {
        if (user.id != null) {
            throw new IllegalArgumentException("ユーザーIDは必須です。");
        }
        if (user.age < 0) {
            throw new IllegalArgumentException("年齢は0以上である必要があります。");
        }
    }
}

public class UserCreator extends UserPersistentBase {
    public User createUser(User user) {
        validateUser(user);
        try {
            HttpClient client = HttpClientFactory.create();
            UserChangeEvent event = new UserChangeEvent(user, WebHookType.CREATE_USER);
            client.post(System.getProperty("WEBHOOK_URL"), event.toString());
        } catch (HttpRequestFailureException e) {
            // webhookのリクエストエラーの場合は通知のみで処理をすすめる
            log.error("failed webhook request", event);
        }
        return repository.create(user);
    }
}

public class UserUpdater extends UserPersistentBase {
    public User updateUser(User user) {
        validateUser(user);
        try {
            HttpClient client = HttpClientFactory.create();
            UserChangeEvent event = new UserChangeEvent(user, WebHookType.UPDATE_USER);
            client.post(System.getProperty("WEBHOOK_URL"), event.toString());
        } catch (HttpRequestFailureException e) {
            // webhookのリクエストエラーの場合は通知のみで処理をすすめる
            log.error("failed webhook request", event);
        }
        return repository.update(user);
    }
}

Webhookのリクエストを行うコードが重複しているので共通化する。

public abstract class UserPersistentBase {
    public void validateUser(User user) {
        if (user.id != null) {
            throw new IllegalArgumentException("ユーザーIDは必須です。");
        }
        if (user.age < 0) {
            throw new IllegalArgumentException("年齢は0以上である必要があります。");
        }
    }

    public void requestWebhook(String message) {
        try {
            HttpClient client = HttpClientFactory.create();
            client.post(System.getProperty("WEBHOOK_URL"), message);
        } catch (HttpRequestFailureException e) {
            // webhookのリクエストエラーの場合は通知のみで処理をすすめる
            log.error("failed webhook request", event);
        }
    }
}

public class UserCreator extends UserPersistentBase {
    public User createUser(User user) {
        validateUser(user);
        requestWebhook(new UserChangeEvent(user, WebHookType.CREATE_USER).toString());
        return repository.create(user);
    }
}

public class UserUpdater extends UserPersistentBase {
    public User updateUser(User user) {
        validateUser(user);
        requestWebhook(new UserChangeEvent(user, WebHookType.UPDATE_USER).toString());
        return repository.update(user);
    }
}

UserPersistentBaseにユーザー永続化時のバリデーションとWebhookの送信処理が追加された。 UserだけではなくItemの作成クラスについても考えてみる。この場合も同様にWebhookの送信が必要である。

public class ItemCreator {
    public Item createItem(Item item) {
        try {
            HttpClient client = HttpClientFactory.create();
            client.post(System.getProperty("WEBHOOK_URL"), new ItemChangeEvent(item, WebHookType.CREATE_ITEM).toString());
        } catch (HttpRequestFailureException e) {
            // webhookのリクエストエラーの場合は通知のみで処理をすすめる
            log.error("failed webhook request", event);
        }        
        return repository.create(item);
    }
}

Webhookの送信処理はユーザーの場合と同様であり、共通化できそうだがそのまま共通化するとこうなる。

public class ItemCreator extends UserPersistentBase {
    public Item createItem(Item item) {
        requestWebhook(new ItemChangeEvent(item, WebHookType.CREATE_ITEM).toString());
        return repository.create(item);
    }
}

UserPersistentBaseを継承したItemCreatorが出来てしまった。 さらにItemについても更新処理とバリデーションを追加する。

public class ItemCreator extends UserPersistentBase {
    public Item createItem(Item item) {
        validateItem(item);
        requestWebhook(new ItemChangeEvent(item, WebHookType.CREATE_ITEM).toString());
        return repository.create(item);
    }
}

public class ItemUpdater extends UserPersistentBase {
    public Item createItem(Item item) {
        validateItem(item);
        requestWebhook(new ItemChangeEvent(item, WebHookType.CREATE_ITEM).toString());
        return repository.create(item);
    }
}

UserPersistentBaseには現在以下の処理がある。

  • ユーザーのバリデーション
  • アイテムのバリデーション
  • Webhookの送信

これはもはやUserPersistentBaseという名前にマッチしない。 PersistentBaseに変更しても良いが今後永続化するリソースが増えたり共通化したい処理が増えるたびにPersistentBaseに処理が追加され続けると、PersistentBaseが肥大化する。 この状況はもはや責務ごとに処理が分割されているとは言いがたい。

責務ごとにクラスを分けつつ継承で共通化した場合、以下のようになる。

public abstract class UserValidator extends WebhookRequester {
    public void validateUser(User user) {
    }
}

public abstract class ItemValidator extends WebhookRequester {
    public void validateUser(User user) {
    }
}

public abstract class WebhookRequester {
    public void requestWebhook(String message) {
    }
}

public class UserCreator extends UserValidator {
    public User createUser(User user) {
        validateUser(user);
        requestWebhook(new UserChangeEvent(user, WebHookType.CREATE_USER).toString());
        return repository.create(user);
    }
}

public class UserUpdater extends UserValidator {
    public User updateUser(User user) {
        validateUser(user);
        requestWebhook(new UserChangeEvent(user, WebHookType.UPDATE_USER).toString());
        return repository.update(user);
    }
}

public class ItemCreator extends ItemValidator {
    public Item createItem(Item item) {
        validateItem(item);
        requestWebhook(new ItemChangeEvent(item, WebHookType.CREATE_ITEM).toString());
        return repository.create(item);
    }
}

public class ItemUpdater extends ItemValidator {
    public Item createItem(Item item) {
        validateItem(item);
        requestWebhook(new ItemChangeEvent(item, WebHookType.CREATE_ITEM).toString());
        return repository.create(item);
    }
}

UserPersistentBaseがUserValidator、ItemValidator、WebhookRequesterに分割された。 コードは確かに分割されたが、UserValidatorにはWebhookRequesterから継承されたrequestWebhookが生えている。

UserValidatorはバリデーションを行うためのクラスだが、バリデーション以外のことをしている。名前と責務が一致していない。

しかも、UserValidatorはWebhookRequesterに依存したり、overrideによってWebhookRequesterの処理に影響を与える事ができる。 例えば、WebhookRequesterの修正によってバリデーションを壊したり、UserValidatorの修正によってWebhookの処理を壊したりする可能性がでてくる。 つまりそれぞれの処理に手を入れる際、親クラスや子クラスを気にしながら手を入れる必要がある。

さらにこの構造では、共通処理が増えるたびに継承の階層が深くなり、何が何を継承しているのかを判断するのが難しくなってくる。 この階層はJavaの制約を満たすためだけに存在する階層で本質的には不要であり、複雑さを増している。

継承をやめてコンポジションで共通化を行うと以下のようになる。

public class UserValidator {
    public void validateUser(User user) {
    }
}

public class ItemValidator {
    public void validateItem(Item item) {
    }
}

public class WebhookRequester {
    public void requestWebhook(String message) {
    }
}

public class UserCreator {
    UserValidator validator = new UserValidator();
    WebhookRequester webhookRequester = new WebhookRequester();
    public User createUser(User user) {
        validator.validateUser(user);
        webhookRequester.requestWebhook(new UserChangeEvent(user, WebHookType.CREATE_USER).toString());
        return repository.create(user);
    }
}

public class UserUpdater {
    UserValidator validator = new UserValidator();
    WebhookRequester webhookRequester = new WebhookRequester();
    public User updateUser(User user) {
        validator.validateUser(user);
        webhookRequester.requestWebhook(new UserChangeEvent(user, WebHookType.UPDATE_USER).toString());
        return repository.update(user);
    }
}

public class ItemCreator {
    ItemValidator validator = new ItemValidator();
    WebhookRequester webhookRequester = new WebhookRequester();
    public Item createItem(Item item) {
        validator.validateItem(item);
        webhookRequester.requestWebhook(new ItemChangeEvent(item, WebHookType.CREATE_ITEM).toString());
        return repository.create(item);
    }
}

public class ItemUpdater {
    ItemValidator validator = new ItemValidator();
    WebhookRequester webhookRequester = new WebhookRequester();
    public Item createItem(Item item) {
        validator.validateItem(item);
        webhookRequester.requestWebhook(new ItemChangeEvent(item, WebHookType.CREATE_ITEM).toString());
        return repository.create(item);
    }
}

UserValidator、ItemValidator、WebhookRequesterは完全に独立していて各クラスの名前と処理の剥離も無い。 完全に独立しているため、WebhookRequesterに手を入れる時はWebhookRequesterのことだけを意識すればよい。

上記のような状態になってしまうことを避けたいという点だけでも継承よりコンポジションを選べる状況なのであれば、コンポジションを選ぶべきだと思う。

継承はいつ使えばいいのか

通化ではなく拡張のために使うものだと思っている。思っています。

まとめ

Javaで継承による共通化を行うと、多重継承ができないため1つの親クラスに全ての共通処理を入れる必要がある。 つまり処理ごとにクラスを分割することができないし、クラスに適切な名前をつけることもできない(なので多くの場合Baseなんとかクラスになってしまう)。

これを避けるために無理やりクラスを分割すると、全く関連の無いクラス同士を親子関係にする必要が出てくる。 こうなると、あるクラスに手を入れる際に関係ないクラスを破壊していないか意識する必要が出てきて辛い。 関係ないクラス同士に親子関係があると、あとから見たときに混乱するので辛い。

Webhookまわりのコード、何?

わかんない

2020年11月にやったこと

このまとめに書いてあるこれからやること以外のことばかりしている

【増補改訂】 財務3表一体理解法

PLとかBSとかなんもわからんという話をしたら会社の人に教えてもらった

これは大変によかった

この辺なんもわからんのをなんとかしようと思って最初簿記の本読んでたんだけど、なんとなく財務諸表の作り方はわかるけどなんでこういう構造になってるのかさっぱりわからん状態になったがそのあたりの疑問がかなり解消された

個人的にこの本は複式簿記の勉強を始めるよりも前に読むとその後の学習がかなり効率よくなる気がする 表をいったりきたりしながら読むのがちょっとつらいので前ページの表と比べなくても差分がわかる構成になっているともっと読みやすい気がした 電書だとそのへんが特に辛いかも でもこれはよかった

Javaパフォーマンス

GCなんもわからんやばいという話をしたら会社の人に教えてもらった

JPAあたりからなんとなく興味がどっかいってしまったがGCの下りはわかりやすくてよかった

わーっと読んだだけで全然触ってないので触る必要がある

その他

クソアプリアドベントカレンダー用にクソアプリを書いたりしていた

これからやること

  • AWS入門する
    • ECS
  • Webエンジニアが知っておきたいインフラの基
  • 入門監視
  • クリーンアーキテクチャ
  • 実践ハイパフォーマンスMySQL 第3版
  • プログラミングコンテスト攻略のためのアルゴリズムとデータ構造
    • 停滞気味
  • Linuxの仕組み
  • Goならわかるシステムプログラミング
  • 英語
  • 中国語
    • Toeic900超えるまで止める予定
  • ここ最近ほとんど業務でプログラミングをしなくなって、調整とかレビューとかばっかしてるがかなり雰囲気でやってるので、マネジメント系の本を読もうとしている
    • なにがいいのかよくわからん
  • Effective Go - The Go Programming Language
  • パタヘネ
  • OAuth2.0のクライアント書く
  • OAuth 2.0/OIDC関連仕様全部読む
  • WebAuthnのドキュメント読む
  • マイクロサービスパターン
  • コンピュータシステムの理論と実装 ―モダンなコンピュータの作り方
  • DBS

2020年9月、10月にやったこと

資格関連ばっかやってた あとは社内で全員 OAuth 2.0 ちゃんと理解しよう勉強会をやってた

ウェブ・セキュリティ基礎試験(徳丸基礎試験)

とった inabajunmr.hatenablog.com

AWS Certified Security Specialty

とった inabajunmr.hatenablog.com 1ヶ月でいけるだろと思って予約したら割とギリギリなかんじでこればっかりになってしまった

Javaパフォーマンス

150ページくらいまでよんだ 読み始めてから資格のほうが以外とやばいことに気づいてそっちにシフトしたので停滞していた GCとかJVMの話面白いのでもうちょっと掘り下げたい気持ちがでてきた とりあえず最後まで読む

英語

9月はシャドーイングしたり単語力の衰えを感じたので

www.amazon.co.jp

をやったりしていた 10月は停滞していた

その他

社内で参加者 OAuth 2.0 全員理解するぞ勉強会をやった

dev.classmethod.jp

参加者に質問してもらったやつでなんでだっけ?ってなったのがあったので整理した

inabajunmr.hatenablog.com

あとはなんかポエムかいてた

inabajunmr.hatenablog.com

inabajunmr.hatenablog.com

これからやること

  • AWS入門する
    • ECS
  • Webエンジニアが知っておきたいインフラの基
  • 入門監視
  • クリーンアーキテクチャ
  • Javaパフォーマンス
  • 実践ハイパフォーマンスMySQL 第3版
  • プログラミングコンテスト攻略のためのアルゴリズムとデータ構造
    • 停滞気味
  • Linuxの仕組み
  • Goならわかるシステムプログラミング
  • 英語
  • 中国語
    • Toeic900超えるまで止める予定
  • ここ最近ほとんど業務でプログラミングをしなくなって、調整とかレビューとかばっかしてるがかなり雰囲気でやってるので、マネジメント系の本を読もうとしている
    • なにがいいのかよくわからん
  • Effective Go - The Go Programming Language
  • パタヘネ
  • OAuth2.0のクライアント書く
  • OAuth 2.0/OIDC関連仕様全部読む
  • WebAuthnのドキュメント読む
  • マイクロサービスパターン
  • 財務3表立体理解法

既にある、という理由で既にあるコードを信じないほうがいい

息の長いプロダクトのコードを見ていると、「なぜか本番にリリースされていない機能」とか「誰もしらない機能」のコードを見つけることがある。 開発途中で機能が不要になったり、一度リリースした機能を潰したり、といった事情は色々あるがとにかくこのようなコードはたまに見つかる。 さらに退職などの理由でこのようなコードについての背景が誰にもわからなくなっていることもある。

そして、その機能がやはり欲しくなったり、コードを部分的に流用したい、といった場合に、このコードをそのまま使いたくなることがある。

この時、このコードを「既にあるから」という理由で信用したくなる時がある。 これはググったら出てきたコードをよくわからないままコピペしたくなる感覚と似ている気がする。 しかし、コピペはNG、という価値観を持っていても既にあるコードはOK、と判断してしまうことがある気がしている。

「既にあるから」という理由でそのコードを信じてはいけない。 コードの背景がわからないということは、そのコードの品質が担保されているのかわからない、ということである。

品質はテストや本番での動作実績に担保されるが、「既にあること」は品質を担保しない。 プロダクトの品質を担保する基準があるのであれば、「既にあるコード」を利用する場合もそのコードが基準を満たしているか確認する必要がある。 満たしていないのであればテストを追加するなどして、基準を満たす必要がある。 ただし、基準を満たしていて工数の削減につながるのであれば、コードの流用はどんどんするべきだと思う。

AWS Certified Security Specialtyをとったので勉強内容をまとめた

f:id:inabajunmr:20201023002915p:plain

ついで f:id:inabajunmr:20201023002919p:plain

前提知識

コードを書く人的なロールでAWSは触っている が、日常的には触るのはSNS、SQS、DynamoDBくらい とはいえメジャーな?サービスはどういうものなのか?くらいは理解している

後はあそびでEC2立てたりECSタスクかいてアプリをデプロイしたりはする

2017年に以下を取得しているが、ろくにAWS触ってない状態で知識だけ詰め込んだので今受けたらアソシエイトも多分おちる

  • AWS Certified DevOps Engineer - Professional
  • AWS Certified Solutions Architect - Professional

セキュリティ系のサービスはIAMはなんとなくわかるけどほかは全然わからん、くらいの感じ

勉強に使った時間

40時間くらい

やったこと

いろんな合格記で紹介されてるやつとだいたい同じような感じ 流れ的には「資格本1冊読む」→「公式の模試」→「公式のトレーニング(Exam Readiness)」→「BlackBelt読みながらちょくちょくガイドみる」→「公式の模試」→「受験」

ホワイトペーパーは一切読んでない 読んでないので良し悪しがわからん

主にBlackBeltとガイド ガイドは網羅的によむというよりもBlackBelt見てて気になったところを見ていった

実際に動かしてどうこうもしてない(すべき)が、Trusted Advisorのチェック項目はマネコンで眺めたりしていた

要点整理から攻略する『AWS認定 セキュリティ-専門知識』

何したらいいのかわからんのでとりあえず買って読んだ セキュリティ系のサービスそもそもなんなのみたいのがわかる これだけで受かるかというと多分受からない

ADの説明がわかりやすいのと回答付き模擬試験がまあまあなボリュームであるのは嬉しい あとは何の勉強したらいいのリストを作るのに使える が、今考えると別に読まなくてもよかった感はある 回答付きの模擬試験を解きたい人は買おう

Exam Readiness: AWS Certified Security - Specialty

なぜ本を読まなくてもいいかというとこのトレーニングでよさそうだったから 何の勉強したらいいのリストを作るのに使える それ以外はまあなんかいいかなという感じのやつ 回答のコツ、みたいのも紹介されてた気がする

BlackBeltとかガイドとか

Exam Readinessで紹介されてたサービスのBlackBeltを片っ端から読むのが勉強のメイン 実際に読んだやつの一覧は以下 なんか漏れてるかもしれんけどだいたいこれで全部だと思う

時間なくてやろうと思ってたけどスルーしたサービスたち

特にCloudWatchとSecretManagerはスルーしないほうがよさそう スルーした

  • CloudWatch
  • DynamoDB
  • Certificate Manager
  • CFn
  • Shield
  • CloudFront
  • RDS
  • CloudHSM
  • SecretManager
  • Route53
  • SSO
  • Cognito
  • Athena

模試

公式の模試を2回やった 初っ端いきなりやって50パーくらいで次に試験前日にやったら75パーだった かなりギリギリ感 本番のスコアは見方がよくわからないので何パーだかわからん

ふりかえり

いきなりBlackBelt、はそこまで間違ってた感じではないけど動画だと時間かかるので、公式のガイド見てピンとこないやつだけBlackBelt、のが効率いい気がした

ガイドのがわかりやすかったり網羅的だったりおもしろかったりするので ガイド読まなすぎてかなりギリギリだった感ある というかそもそも明らかに試験にでそうなサービスを見切れてないのがまずかった

あとおもしろはんぶんでプラクティショナーも同日にうけた こっちは準備してなくても受かったけどぜんぜん知らん範囲の問題が多かった

とってみてどうだった

AWSのセキュリティにくわしくなったかもしれない