OmniAICreator/Qiita-1.07M huggingface dataset:
あれどこに何個をあったっけ？をAIですぐさま解決！！1人に1台どうですか？

## Teachable MachineとLINE Botを使って、スピーディーな判断を！
皆さん、こんにちは！私は北海道の小売業で働いているサラリーマンです！
普段の業務は営業ではなく、会社として**環境や社会への貢献に取り組む業務**をメインに働いています。その業務では、お店がより**エコな資材**を使用できるようにすることも仕事の一つです。例えば、レジで飲み物やお弁当を購入されたお客さまに**紙製ストロー**や**木製スプーン**をお渡しすることがあります。そして、たまに店舗巡回している時に「この資材が足りないのですが本社から送ってもらえませんか？」と要望を受ける時があります。その際、画像認識による資材判別を行うことにより、似たような資材が何十種類もある中からすぐにその資材の情報を確認でき、その場で在庫があるなしの返答ができます。在庫があった場合はいつまでに店舗にその資材を送れると返答ができ、在庫がなかった場合は、すぐに発注をするという判断ができます。
今回は、**Teachable Machine**と**LINE Bot**を使いスムーズにその資材の在庫の数を入手します！！

## 使用したツール
**●Node-RED**　
<img width="600" alt="Node-red.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2621180/1bf7cddf-5feb-f30d-d843-15f7aaee6132.png
">
**●Teachable Machine**　
<img width="600" alt="Teachable Machine.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2621180/fb4b6d95-9001-0aa1-02cc-c481b2f1b445.png
">

**●Make**
<img width="600" alt="Make.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2621180/200560fb-46c5-4659-52e3-fd7c8b84466d.png">
**Googleスプレッドシート**
<img width="600" alt="スプレッドシート.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2621180/45cd9cf8-33a1-4118-1ee2-7c53bc428f2f.png">

**●LINE Bot**
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ラインで在庫管理始めてみませんか？<a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a> <a href="https://twitter.com/hashtag/TeachableMachine?src=hash&amp;ref_src=twsrc%5Etfw">#TeachableMachine</a> <a href="https://twitter.com/hashtag/nodered?src=hash&amp;ref_src=twsrc%5Etfw">#nodered</a> <a href="https://twitter.com/hashtag/LINEBOT?src=hash&amp;ref_src=twsrc%5Etfw">#LINEBOT</a> <a href="https://t.co/HTY0vlMYBH">pic.twitter.com/HTY0vlMYBH</a></p>&mdash; 佐々木広大 (@sasakikoudai930) <a href="https://twitter.com/sasakikoudai930/status/1527629134670594048?ref_src=twsrc%5Etfw">May 20, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## LINE Botに返事が返ってくるまでの流れ
**①Node-REDのカメラを使い必要な資材を撮影**
**②Teachable Machineで学習させたモデルで判別**
**③Makeは、Node-REDからTeachable Machineの答えを抽出しGoogleスプレッドシートからその資材の保管場所、在庫状況を取得しLINE Botへ返答**

## やりたかったこと
本当は、**LINE Bot**で写真と撮り、その画像データが**Node-RED**を介して**Teachable Machine**に情報が行き画像判別して、またその情報を**Node-RED**から**Make**に送り、**Make**と連携した**Googleスプレッドシート**からその資材の保管場所、在庫状況を取得し**LINE Bot**へ返信が来るように作りたかったのですが、知識が足りず最初の段階の**LINE Bot**から**Node-RED**へ画像データを送信することが出来ず実装には至りませんでした。考えた手段としては、**MakeでLINE Botで投稿された写真をデータ化**し、**Node-RED**へ転送、**Teachable Machine**で判別し**LINE Bot**へ返す方法。もしくは、**Node-red**で**LINE Bot**から写真のデータを取り込むフローを作り、**Teachable Machine**で判別し**LINE Bot**へ返す方法。どちらも実装には至らなかったので、もっと勉強してやり遂げたいです。

## あとがき
今回、作成した**Teachable Machine**と**LINE Bot**での在庫管理ツールは、色々なところで活用できると考えています。実際、返答内容は**Googleスプレッドシート**の内容を書き換えるだけでいいので、活用機会は無限大です。まだ、未完成の状態なのでしっかり完成させて活用していきたいです。

最後まで読んでいただきありがとうございます。
EC-CUBE4 フォントの追加方法


## フォントの追加方法
EC-CUBE4系はPDFの出力にTCPDFを利用している。
TCPDFのフォント追加ツールを利用し、フォントファイルを変換し追加する。

```
vendor/tecnickcom/tcpdf/tools/tcpdf_addfont.php -i 追加するフォントファイル(ttf形式)
```

`Process successfully completed!`と表示されれば成功。

下記は、HG行書体を追加する場合
```
vendor/tecnickcom/tcpdf/tools/tcpdf_addfont.php -i HGGyoshotai.ttf

>>> Converting fonts for TCPDF:
*** Output dir set to /vendor/tecnickcom/tcpdf/fonts/
+++ OK   : HGGyoshotai.ttf added as hggyoshotai
>>> Process successfully completed!
```

`vendor/tecnickcom/tcpdf/fonts`に3つのファイルが追加される。
- hggyoshotai.ctg.z
- hggyoshotai.php
- hggyoshotai.z

## フォントの設定
追加したフォントを設定する。
下記は、納品書のフォントを上記処理で追加したHG行書体に変更する例
```diff_php:src/Eccube/Service/OrderPdfService.php
- $this->SetFont(self::FONT_SJIS);
+ $this->SetFont('hggyoshotai');
```

デフォルト
![スクリーンショット 2022-05-20 21.43.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1140333/7ab3704f-4e4b-2cc8-4880-6d29f38a6da8.png)

変更後
![スクリーンショット 2022-05-20 21.45.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1140333/925a1816-75c1-e9a7-a394-2f65de412475.png)




## 参考文献
[TCPDFにフォントを追加する方法](https://xiyuan.jp/php/427/#i-3)
brew upgrade voltaしたら、nodeにPATHが通らなくなった時の解決法

# 環境

- macOS 12.3.1
- Homebrew 3.4.11
- Volta 1.0.5 -> 1.0.7

# Volta

Voltaはクロスプラットフォームで使えるNodeとnpmのバージョン管理ツールです。かなり高速で、使い勝手も良いので、未導入の方は是非。

https://docs.volta.sh/guide/

# upgrade

voltaを最新にアップグレード

```terminal
$ brew upgrade volta
```

# command not found: node

nodeはおろかnpmも。焦る。

```terminal
$ node -v
zsh: command not found: node

$ npm -v
zsh: command not found: npm
```

けど、voltaだけはPATHが通っている状態

```terminal
$ volta -v
1.0.7
```

# 解決策

voltaをsetupすれば直りました。
もしかしたらupgradeした時にターミナルに出力されてたのかも。

```terminal
$ volta setup
success: Setup complete. Open a new terminal to start using Volta!
```

ターミナルを再起動したらnodeのPATH通った！

```terminal
$ node -v
v16.13.0
```
【Next.js】ISRの実装方法

## ISRの実装方法
以下のようにすることでISRを実装できます。

```pages/posts.tsx
import { NextPage, GetStaticProps } from 'next';

type Props = {
  posts: Post[
    < 省略 >
  ]; 
}

export const getStaticProps: GetStaticProps<Props> = async () => {
  const posts = await getPosts();
  return {
    props: {
      posts,
    },
    revalidate: 10 
};

const Page: NextPage<Props> = (props) => {
  return (
    <ul>
      {posts.map((post) => (
        <li>{post.title}</li>
      ))}
    </ul>
  );
};

export default Page;
```

```posts/[id].tsx
export const getStaticProps: GetStaticProps<Props> = async () => {
  const posts = await getPosts();
  return {
    props: {
      posts,
    },
    revalidate: 10
  };
};

 export const getStaticPaths: GetStaticPaths = async () => {
   return {
     paths: [], 
     fallback: 'blocking',
   };
 };

export default (props: Props) => {
  return (
    <ul>
      {posts.map((post) => (
        <li>{post.title}</li>
      ))}
    </ul>
  );
};
```
VSCodeでGit Graphが見れなくなった。

```sql
Error: Unable to load Commits
There are no commits in this repository.
```
![スクリーンショット 2022-05-20 21.13.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1134006/89a0ed6f-2679-c898-f703-92c3fb27738f.png)
突然`Git Graph`が表示されなくなって、ビックリ。ログを見ても原因不明。下記コマンドでエラーメッセージで失敗している状態を回避しようとしても無理。
```taminal
git log --max-count=301 --date-order --branches --tags --remotes HEAD --
git show-ref -d --head
git status --untracked-files=all --porcelain
```
特定の`Git`実行可能ファイルを強制的に`Visual Studio Code`および`Git Graph`で使用するように仕向けても無理。
```taminal
git.path": "C:\\Program Files\\Git\\cmd\\git.exe
git rev-parse --show-toplevel
```
拡張機能を再インストールし直したり、再起動してもビクともしない。そんな時は、諦めて、一度ディレクトリを消して再度`Git Clone`し直しましょう。プロジェクトのファイルのズレで生じる場合はこちらでリポジトリを直に指定できるので直るかと！
Powerautomate 　BPFのテーブルを更新する方法

①BPFのテーブルのプロセスは、すべてシステムテーブル「プロセス ステージ」に保存しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368087/d105f839-f534-78b1-a5c7-2dd45ebdfad8.png)

②グランドフローでBPFを更新するには、システムテーブル「プロセス ステージ」から、stagenameをフィルターして、
プロセス ステージIDを取得する必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368087/4aca6163-b310-df5e-763c-ade5a2c26da8.png)

③BPFテーブルの「アクティブ ステージ (プロセス ステージ)」にprocessstages（first(outputs('プロセスステージ「４」のIDを取得する')?['body/value'])?['processstageid']）入力し、保存する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368087/7af3992a-8b13-6dec-242d-2d5163213fc8.png)
PowerAutomate  選択肢の空判定方法

dataverseの選択肢式のカラムは空を判定する場合、
直接「empty(triggerBody()?['category'])」を使うと、エラーになりました。
原因は、選択肢式の戻り値がIntgerのためです。

対応方法は、「empty(string(triggerBody()?['category']))」です。
PowerAutomate のトリガーの起動する条件設定方法

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368087/f74020c6-a1fa-977a-c892-a4842933eb89.png)

トリガーの条件
トリガーを起動するために true にすべき 1 つまたは複数の式を指定します。
@and(
  not(empty(triggerBody()?['status1'])),
  not(empty(triggerBody()?['status2'])),
  not(empty(string(triggerBody()?['status3']))),
  or(
    equals(triggerBody()?['status4'],'②'),
    equals(triggerBody()?['status4'],'③')
  )
)
tailwind.cssで長い文字列を・・・にする方法【複数行】


<h3>一行のみ</h3>

```html
<div className="truncate">一行のみを・・・にする場合</div>
```

<h3>複数行</h3>
ライブラリを導入

```
yarn add @tailwindcss/line-clamp
```

```tailwind.config.js
module.exports = {
  plugins: [require('@tailwindcss/line-clamp'),],
};
```

<h5>２行</h5>

```html
<div className="line-clamp-2">
  【２行】ゴリラゴリラゴリラゴリラゴリラ ゴリラ ゴリラ ゴリラ ゴリラ ゴリラ
  ゴリラ ゴリラ ゴリラ ゴリラ ゴリラ ゴリラ ゴリラ ゴリラ ゴリラ ゴリラ ゴリラ
  ゴリラ ゴリラ ゴリラ
</div>
```

<h5>３行</h5>

```html
<div className="line-clamp-3">
  【３行】ゴリラゴリラゴリラゴリラゴリラ ゴリラ ゴリラ ゴリラ ゴリラ ゴリラ
  ゴリラ ゴリラ ゴリラ ゴリラ ゴリラ ゴリラ ゴリラ ゴリラ ゴリラ ゴリラ ゴリラ
  ゴリラ ゴリラ ゴリラ
</div>
```
AzureDevOpsパイプラインで、環境変数を設定する



## 背景

リリース作業の一連のパイプラインの中で、Azureリソースに対する環境変数の設定を手動で行う必要があった

⇒

Azureリソースに対する環境変数の設定もパイプラインから実施する





## 対応



### AzureDevOpsのLibraryに環境変数を登録する



Pipelines > Library > 「+ Variable group」を押下



設定したい環境変数を定義して「Save」を押下

![image-20220520195327824.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502798/814a64fd-a901-1575-e661-56a02d4c6257.png)



### パイプライン(yaml)側で、登録したLibraryを読み込んでAzureリソースに設定する



下記でLibraryに登録したvariablesを読み込み

```yaml
variables:
- group: environment-variable
```



読み込んだvariablesは下記のように使える

```yaml
"value": "$(CONNECTION_STRING)",
```



```yaml
# variables groupから環境設定を読み込み
variables:
- group: environment-variable

steps:
# 環境変数設定
- task: AzureAppServiceSettings@1
  condition: eq(variables['Build.SourceBranch'], 'refs/heads/develop')
  displayName: 環境変数設定
  inputs:
    azureSubscription: xxxxx
    resourceGroupName: xxxxx
    appName: xxxxx
    slotName: stage
    appSettings: |
      [
        {
          "name": "SQL_SERVER_CONNECTION_STRING",
          "value": "$(CONNECTION_STRING)",
          "slotSetting": true
        },
        {
          "name": "XXX_XXX_XXX",
          "value": "$(xxxxxxxx)",
          "slotSetting": true
        },
        {
          "name": "XXX_XXX_XXX",
          "value": "$(xxxxxxxx)",
          "slotSetting": true
        },

      ]
```



補足：

`condition`プロパティで、該当のtaskの実行条件を定義できる。

下記は`develop`ブランチでパイプラインが動作しているときのみtaskを実行する。

```yaml
  condition: eq(variables['Build.SourceBranch'], 'refs/heads/develop')
```



↓こんな感じで保護ブランチを`condition`に指定しておけば、開発/本番環境向けに設定する環境変数を分岐することができる

```yaml
  condition: eq(variables['Build.SourceBranch'], 'refs/heads/production')
```
AzureDevOpsパイプラインで、AppServiceのスロットをスワップする


## 背景

リリース作業の一連のパイプラインの中で、AppServiceのスロットスワップが手動で行う必要があった

⇒

AppServiceのスロットスワップもパイプラインから実施する



## 対応



### スワップ用のパイプラインを作成



下記yamlを定義する。

```yaml
parameters:
- name: TargetSlot
  displayName: TargetSlot
  type: string
  default: stage
  values:
  - stage
  - sorry
  - production
- name: SourceSlot
  displayName: SourceSlot
  type: string
  default: stage
  values:
  - stage
  - sorry
  - production

# Azureリソースのスワップ
steps:
- task: AzureAppServiceManage@0
  displayName: スロットのスワップ
  inputs:
    azureSubscription: xxxxx
    Action: Swap Slots
    SwapWithProduction: false
    WebAppName: xxxxx
    ResourceGroupName: xxxxx
    TargetSlot: ${{ parameters.TargetSlot }}
    SourceSlot: ${{ parameters.SourceSlot }}
```



`parameters`をパイプライン実行時に指定することで、スワップ対象のスロットを選択できるようにしている。

(パイプライン実行時に↓こんな感じで選べる)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502798/f13486c2-54dd-cb0c-b625-ca6a7d6dcfcd.png)




※AzurePortal側の環境変数の設定で「デプロイスロットの設定」にチェックがついていない環境変数はスワップ時に置いてかれます。。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502798/4c219f4f-348c-de43-54a2-59a7fe73f071.png)






## 参考

https://docs.microsoft.com/ja-jp/azure/devops/pipelines/tasks/deploy/azure-app-service-manage?view=azure-devops#yaml-snippet
Flutterで作った簡単なメモアプリのソース公開

## 初めに
個人開発で2つのFlutterアプリを作りました。
![teslog.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37929/806820bd-6e8c-0b05-25d0-0a7ce62f461d.png)

https://play.google.com/store/apps/details?id=com.murapon.self_scoring_app

![ダウンロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37929/6c201dc3-ea55-0d30-76ed-0b9c785976fa.png)

https://play.google.com/store/apps/details?id=com.murapon.hashtag_keeper_app

それらのアプリのソースを元にして、FlutterとFirebaseを使った簡単なメモアプリを作ったので公開します。一言メモを登録できるだけ、デザインの装飾一切なしのシンプルなアプリです。
flutterの基本的な部分は勉強したけど、そこからどうアプリを作ったらいいからわからない方に、1つの例として参考になればと思ってます。Android Studioのシミュレーターで動作確認済みです。

## 前提
- Flutter初心者が作ってます。簡単なアプリを作る際の参考にしてください。もっといい設計やソースもあると思います。
- パッと見て理解しやすいように、あえてシンプルな構成とソースにしています。
- 実際にアプリを作る際は、クラスを細分化したり、widgetを部品化して共通で利用できるようにしたり、固定の表示文言を別ファイルでまとめて管理するなどした方がよいです。

## サンプルアプリのソース
https://github.com/murapon/sample-flutter-firebase-app

## サンプルアプリの主な機能
- メモの登録、編集、削除、一覧表示ができます。
- 新しいバージョンが出た際に、強制バージョンアップを促すことができます。
![screen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37929/b5339abb-13d2-08f4-83f9-1bc78757f138.png)


## サンプルアプリの動かし方
1. ソースをダウンロードする。
2. Firebaseにプロジェクトを作成する。
3. Firebase Cloud Firestoreを開始し、「app_version」というコレクションを作成し、以下のようなドキュメントを追加する。ドキュメントIDは[DBAppVersion.dart](https://github.com/murapon/sample-flutter-firebase-app/blob/main/lib/storage/DBAppVersion.dart)に記載して使うのでそれに合わせる。build_numberは1で。android_urlは、実際にアプリリリースしたらそこにアプリのダウンロードURLを入れる。
![スクリーンショット 2022-05-17 9.19.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37929/22eab45e-e346-8757-e219-b7d7013a4cf8.png)
4. インデックスを追加する。メモを登録するコレクションはプログラムを動かした際に自動で作られるが、そこのインデックスは事前に作っておく必要がある。
![スクリーンショット 2022-05-19 10.12.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37929/d7b9c740-10d7-01bc-623b-b44d531efa8d.png)
5. Firebase Authenticationに、「匿名」プロバイダーを追加する
![スクリーンショット 2022-05-18 22.22.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37929/5729ef4c-ce3e-b3c6-ac87-158afce9ffe0.png)
6. プロジェクトの設定から、androidアプリを追加し、google-services.jsonをダウンロードする。パッケージ名は、com.example.sample_flutter_firebase_appにするとスムーズに動作確認できると思います。
![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37929/78af3a14-3f81-df61-7845-8ed74c19e402.png)
7. android/app/以下に、google-services.jsonを設置して、Android Studioでアプリを実行する。（Flutter SDKのPathエラーが出たら、[ここ](https://nzigen.com/reference/flutter/2018-04-14-android-studio/)などを参考に設定してください）

## 発展
このアプリを参考に、登録できる項目を増やしたり、デザインを整えれば、TODOや、買い物メモを管理するアプリなどが簡単にできると思います。
さらに、各種ライブラリー使えば、[てすろぐ](https://play.google.com/store/apps/details?id=com.murapon.self_scoring_app)、[ハッシュタグキーパー](https://play.google.com/store/apps/details?id=com.murapon.hashtag_keeper_app)といったアプリも作れます。他にもツール系アプリならこれをこのアプリをベースに作ることができるので参考にしてください。
typescriptのanyとunknown

```typescript
let foo: any = 123;
console.log(foo.msg); // OK
let a_value1: unknown = foo;   // OK
let a_value2: any = foo;      // OK
let a_value3: string = foo;   // OK


let bar: unknown = 222; // OK 
console.log(bar.msg); // Error
let k_value1: unknown = bar;   // OK
let K_value2: any = bar;      // OK
let K_value3: string = bar;   // Error
```
typescriptはanyに対して、何のタイプチェックはしません。
typescriptはunknownに対して、タイプチェックをする。barはunknownなので、msgにアクセスできるかどうかは分からなくて、一応アクセスを禁止とする。
unknownは anyとunknown以外の変数に代入できない
anyは どんな変数にも代入できる
Javascriptでタグの属性（classやstyle）を指定して削除する方法

## 方法

参考にした記事
https://www.javadrive.jp/javascript/dom/index3.html
https://www.javadrive.jp/javascript/dom/index18.html
https://www.tam-tam.co.jp/tipsnote/javascript/post601.html


### Jqueryの場合

```javascript
 $(function() {
   $("span").removeAttr("style");
   $("span").removeAttr("width");
});
```




### Vanilla JSの場合

```javascript
<script>
/*ページ読み込み時にspanタグに埋め込まれたstyle="〇〇px"を削除するメソッド*/
window.onload = function(){
document.querySelector('.select2').removeAttribute('style');
}
</script>
```


### ポイント
Vanilla JSでの実装時はJqueryと異なり、window.onlaoadを使用し、ファイル読み込み時にメソッドが動く様に記述を行った。
【JavaScript】正規表現で数値を取得し、変数代入する

## はじめに
`http://localhost:3000/user/1'`などのパスの最後の数値を取得し変数代入することがあったので方法を記録しておく。

## 結論

```javascript
// 対象となる文字列
const text = 'http://localhost:3000/user/123';

// 正規表現を定義
const regex = /(?<=user\/)([0-9]+)/;

// "()" で指定した箇所が $1　に入る
const found = text.match(regex);

// found[0]で取得可能
console.log("found", found[0])
=> 123
```


### 詳細
`(?<=user\/)`の部分は後読みアサーションといい、上記の例では、取得したい数字の前に、`user/`が存在する場合のみ、その数字がマッチするようになる。

あとは、`text.match(regex)`でmatchさせたときに、変数`found`には配列でデータが入るので、その一つ目を取得すればいい。


## おわりに
先読み・後読みアサーションについて理解することができた。今後も必要に応じて正規表現の使い方を調査していきたい。
AzureDevOpsパイプラインで、npmライブラリの脆弱性チェックを定期的に実施する


## 背景

使用しているnpmライブラリにいつの間にか脆弱性が含まれてしまい、それを知らないうちに放置していた。

⇒

定期的に脆弱性チェックを行うことで、脆弱性が含まれたことを検知したい。



## 対応



### パイプラインからnpm auditを行う



下記の`command: 'custom'`で指定したディレクトリに対して`npm audit`を実施する

```yaml
    steps:

    # Nodeインストール
    - task: NodeTool@0
      inputs:
        versionSpec: '14.16.0'
      displayName: 'Install Node.js'

    # npm ci
    - script: |
        npm ci
      displayName: 'npm ci'
      workingDirectory:  '$(System.DefaultWorkingDirectory)'

    # 脆弱性high以上が含まれるかチェック
    - task: Npm@1
      inputs:
        command: 'custom'
        workingDir: '$(System.DefaultWorkingDirectory)'
        customCommand: 'audit --registry=https://registry.npmjs.org/ --audit-level=high'
      displayName: 'npm audit'
```



※`--registry=https://registry.npmjs.org/`このオプションについては、Azure公式Docで必要と記載があるのでつけてます。(registryを明示してあげないといけないっぽい？/デフォルトのregistry指定では動作しない？)

https://docs.microsoft.com/ja-jp/azure/devops/artifacts/npm/npm-audit?view=azure-devops&tabs=yaml#run-npm-audit-from-your-pipeline



### パイプラインを定期的に実行する



下記記載により、パイプラインの定期実行を構成する。

```yaml
schedules:
- cron: '0 10 * * *'
  displayName: '毎日19:00起動'
  branches:
    include:
    - develop
```



上記設定後、AzureDevOps > Pipelinesの該当パイプラインの設定から「Scheduled runs」を押下すると、設定を確認することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502798/aec3c1b5-855d-9e63-70f9-f5446aed6c6b.png)

`npm audit`でhigh以上のものを検知すると、パイプライン自体が失敗するようになっています。
【AtCoder】ABC251 E問題 解説

# 問題

隣接する数字ペアを選択していき、最終的に全部の数字が選択されるようにペアを選択したときの最小コストを求める問題。

https://atcoder.jp/contests/abc251/tasks/abc251_e

# 方針
1次元dpで解くことができる。計算量は $O(N)$。
具体的には以下の配列を定義しておく。

- $dp[i]$ := $1$ から $i$ までの数字を全て網羅するようなペアの選択をした時のコストの最小値

$dp[i]$ を更新しようと考えるとき、見るべき値は以下の3つで、このうち最小の値を $dp[i]$ として更新すればいい。

- $dp[i]$
- $min(dp[i-2],dp[i-1]) + a[i-1]$
- $dp[i-1] + a[i]$

最後の値が最小の時には $dp[i+1]$ の更新も行う必要があるので注意が必要。

また、$A_N$ だけややこしいので、$A_N$ を使うときと使わないときの答えをそれぞれ求める方針で解いた。
使うときは $dp[1] =0$ として $dp[N-1] + a[N]$、使わないときは $dp[N]$ が答えになり、小さい方を出力すればよい。
![Mar 18.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2665102/9ef22e96-ff6e-7bad-9577-e3fa3286c210.jpeg)

# 実装
難易度的にこれがDなのでは...
Dが難しいなら執着せずにEに移るようにしようと反省。

```cpp:ABC251_E.cpp
const int max_n = 300005;
ll a[max_n];
ll dp[max_n];
int n;

int main() {
	std::cin >> n;
	repa(i, n) { (void)scanf("%lld", &a[i]); }

	ll ans = infl;

	//A_N を用いない場合
	repa(i, n) { dp[i] = infl; }
	dp[0] = 0;
	dp[1] = dp[2] = a[1];
	repa(i, n - 2) {
		bool flag = false;
		dp[i + 2] = min(dp[i + 2], min(dp[i], dp[i + 1]) + a[i + 1]);
		if (dp[i + 2] >= dp[i + 1] + a[i + 2]) { flag = true; dp[i + 2] = dp[i + 1] + a[i + 2]; }
		if (flag && i + 3 <= n) { dp[i + 3] = dp[i + 2]; }
	}
	ans = dp[n];

	//A_N を用いる場合
	repa(i, n) { dp[i] = infl; }
	dp[0] = 0;
	dp[1] = 0;
	rep(i, n - 2) {
		bool flag = false;
		dp[i + 2] = min(dp[i + 2], min(dp[i], dp[i + 1]) + a[i + 1]);
		if (dp[i + 2] >= dp[i + 1] + a[i + 2]) { flag = true; dp[i + 2] = dp[i + 1] + a[i + 2]; }
		if (flag && i + 3 <= n - 1) { dp[i + 3] = dp[i + 2]; i++; }
	}
	ans = min(ans, dp[n - 1] + a[n]);

	cout << ans << endl;

	return 0;
}
```
AzureDevOpsパイプラインで、ユニットテストのカバレッジを出力する


## 背景



ユニットテストをjestで用意しているが、コードに対してどれくらいカバーできているか不透明であった。

(現在のテストコードがソースに対して充実しているか把握できていない。)

(カバーすべき対象が把握できていない。)

⇒

パイプラインでユニットテストを実施しているので、そのタイミングでカバレッジ(テストの網羅率)を出力して、定期的にチェックする。



## カバレッジの出力方法(jest)



### jestの設定ファイルを修正し、カバレッジ出力先を指定する



`jest.config.js`に下記設定を追加する。

```jest.config.js
// カバレッジ取得するか否か
  collectCoverage: true,

// The directory where Jest should output its coverage files
// カバレッジ出力先
  coverageDirectory: 'coverage',

// A list of reporter names that Jest uses when writing coverage reports
// レポータ名
  coverageReporters: [
    'html', //開発者ローカルで確認用
    'cobertura' //CIパイプラインで確認用
    // "json",
    // "text",
    // "lcov",
    // "clover"
  ],
```



### test実施時に、設定ファイルを読み込んで実施するようにオプションを加える

`packages.json`の`script`を下記のように設定する。

(設定ファイルの場所は任意)

```package.json
  "scripts": {
    "test": "jest --config test/unit/jest.config.js --reporters=default",
  },
```



ここまでの設定で、`npm run test`を実行すれば`test/unit/coverage`にカバレッジが生成されるようになります。



### yaml上で、カバレッジ出力タスクを追加する



`pipeline.yml`に、ユニットテスト後に`PublishCodeCoverageResults@1`タスクを追加する。

```pipelien.yml
### ユニットテスト
- script: |
    npm run test
  displayName: 'ユニットテスト'
  workingDirectory:  '$(System.DefaultWorkingDirectory)'

### Coverageの取得
- task: PublishCodeCoverageResults@1
  displayName: 'Coverageの取得'
  inputs:
    codeCoverageTool: Cobertura
    summaryFileLocation: $(System.DefaultWorkingDirectory)/test/unit/coverage/cobertura-coverage.xml
```



これで、パイプラインの実行結果画面から、カバレッジが参照できるようになります。



パイプラインの実行結果画面に、「Summary」の隣に「Code Coverage」タブが現れる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502798/09a9c34a-5149-d690-4deb-ba1b99d22469.png)




## 補足

カバレッジは100%目指せばいいものでもないらしい。。

https://zenn.dev/ikemo/articles/test-coverage-100-percent
【Maven】Could not find artifact sun.jdk:tools:jar:1.8.0 at specified path

Mavenにて `clean package` を実行したときに以下のエラー
```
Could not find artifact sun.jdk:tools:jar:1.8.0 at specified path
```

# 環境
Windows
Maven v3.8.5
java v1.8.0
Jdk v1.8.0

# 結論
環境変数内のJAVA_HOMEがJREを向いていたため、JDKに修正すると実行された。
エラーの中でruntimeがJREになっていたら、JDKになおしましょう。

# 参考
https://www.eclipse.org/forums/index.php/t/1091063/
AzureDevOpsのパイプラインにおいて、npmキャッシュをパイプラインVM上に保管し、パイプライン実行時間を短縮する

# AzureDevOpsのパイプラインにおいて、npmキャッシュをパイプラインVM上に保管し、パイプライン実行時間を短縮する



## 背景

保護ブランチにマージ時にパイプラインを実行しているが、実行時間が長すぎてデプロイしたいときにかなりの待ち時間(最大40分とか)が発生していた。

⇒

保護ブランチにマージ時のチェックは必要最低限にする対応をしたものの、まだ10分とかかかってしまう。

(build,testだけしてArtifactとかも省略)

⇒

時間のかかっていたnpm i の実行時間を短縮するために、キャッシュを使えないか検討した。



## サマリ

実行時間を、平均15分から平均7分程度に削減できた。



## 対応内容

### npmキャッシュをパイプラインに保管する



下記タスクを使用すると、Azureのパイプライン上でキャッシュを保管できる。

```
variables:
  npm_config_cache: $(Pipeline.Workspace)/.npm

- task: Cache@2
  inputs:
    key: $(System.DefaultWorkingDirectory)/package-lock.json
    path: $(npm_config_cache)
  displayName: 'npmライブラリのキャッシュ利用'
```



`key`：キャッシュのキー。ここで指定した物が変わるとキャッシュを取り直す。

`path`：キャッシュとして保管する対象。今回の場合は`.npm`を指定※



※npmは`.npm`フォルダに以前インストールしたパッケージのバージョンとかを残しているらしい。

※`.npm`フォルダはプロジェクトフォルダ配下にできるわけじゃなく、マシンの`home`配下とかにできるため、`$(Pipeline.Workspace)`から参照してる。



以下パイプライン全体

```yaml
variables:
  npm_config_cache: $(Pipeline.Workspace)/.npm

pool:
  vmImage: 'windows-latest'

steps:
- task: NodeTool@0
  inputs:
    versionSpec: '14.16.0'
  displayName: 'Install Node.js'

- task: CmdLine@2
  displayName: Set Pipeline Timezone
  inputs:
    script: tzutil /s "Tokyo Standard Time"

### npmライブラリのキャッシュ利用
- task: Cache@2
  inputs:
    key: $(System.DefaultWorkingDirectory)/package-lock.json
    path: $(npm_config_cache)
  displayName: 'npmライブラリのキャッシュ利用'

### npmライブラリのインストール
- script: |
    npm i
  displayName: 'npmライブラリのインストール'
  workingDirectory:  '$(System.DefaultWorkingDirectory)'

### ユニットテスト
- script: |
    npm run test
  displayName: 'ユニットテスト'
  workingDirectory:  '$(System.DefaultWorkingDirectory)'


### ビルド
- script: |
    npm run build-dev
  displayName: 'ビルド'
  workingDirectory:  '$(System.DefaultWorkingDirectory)'


```



## 実施結果

Before：実行時間平均15分くらい
↓
After：実行時間平均7分くらい



## 参考

https://docs.microsoft.com/ja-jp/azure/devops/pipelines/release/caching?view=azure-devops#conditioning-on-cache-restoration



https://docs.microsoft.com/ja-jp/azure/devops/pipelines/tasks/utility/cache?view=azure-devops
spreadsheetとAppsScriptでスケジュール表作成

![!スクリーンショット (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1923997/705353e8-2a90-9360-4609-cb94e13e3e6b.png)



```JavaScript:AppsScriptのコード
function myFunction() {
 let spreadSheetByActive = SpreadsheetApp.getActive()
 
 let range = spreadSheetByActive.getSheetByName("sheet1").getRange("H2:I2")
 let date = spreadSheetByActive.getSheetByName("sheet1").getRange("D2")
 let data_holder = spreadSheetByActive.getSheetByName("sheet3").getRange("D1")
 let values = range.getValues()
 let dates = date.getValues()
 let count = data_holder.getValues()

 spreadSheetByActive.getSheetByName("sheet3").getRange(count,2,1,2).setValues(values)
 spreadSheetByActive.getSheetByName("sheet3").getRange(count,1).setValues(dates)
 spreadSheetByActive.getSheetByName("sheet3").getRange("D1").setValues([[++count]])

 let sh = spreadSheetByActive.getSheetByName("sheet1").getRange("C6:L24");
 sh.clearContent();
}
```
【TypeScript】配列から重複を排除する方法

# Setを使用する
- Setとは、重複する値がないことを保証したコレクション

## 配列との違い
- 順序をもたない
- インデックスによるアクセスはできない

## サンプルコード
```ts
const set = new Set([1, 2, 2, 3]);
console.log(set); // {1, 2, 3}
```
CloudWatch SyntheticsでCanary削除時にリソースも削除されるようになった

# はじめに
CloudWatch SyntheticsのCanaryを削除すると、同時に使用しているリソースも削除されるようになったという発表がありました。
こちら個人的に結構嬉しいアップデートだったので、紹介します。

https://aws.amazon.com/jp/about-aws/whats-new/2022/05/amazon-cloudwatch-synthetics-support-canary-resources-deletion/

# どういうことか
CloudWatch Syntheticsはエンドポイントの外形監視を行うサービスです。
定期的にエンドポイントを叩きに行って死活監視を行ってくれます。
詳しくは公式ドキュメントをご参照ください。

https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries.html

CloudWatch SyntheticsはCanaryを作成する際にLambdaを作成します。(Lambdaでスクリプトを定期的に実行することにより、エンドポイントの監視を行います。)

以前までは、Canaryと同時に作成されたLambdaはSyntheticsコンソール**削除できません**でした。削除するにはLambdaコンソールからLambdaを探して削除する必要がありました。

しかし、今回のアップデートによりCanaryを消すだけでLambdaやRoleも同時に消されるようになりました。(嬉しい)
これによってLambdaの消し忘れがなくなりゴミリソースが減りますね！

# ためす
試してみます。..といっても作成して削除するだけです。

適当にCanaryを作りました。
(IAM Roleを適当につけたせいでS3へのアクセス権限がなく、失敗しました。)
![image_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675893/c31ae45a-9907-21ea-ccfb-360bd51adc28.png)

当然Lambdaも作成されます。
![image_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675893/17703786-5cc7-897f-6e7c-c3cba95a9594.png)

SyntheticsのコンソールからCanaryを消します。
すると、削除確認画面が表示されました！以前までは存在しませんでした。
![image_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675893/7cc1a4fc-8e68-3de1-dd06-dba1bf82762c.png)

数秒で削除が完了しました。
試しにLambdaやIAMから当該リソースを検索しましたが、存在しませんでした。
![image_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675893/3aead321-38b9-2679-588a-00e99ccccea3.png)

以上です。

CloudWatch Syntheticsの細かいアップデートの話でした。
個人的に気になっていた(地味に嫌な)部分だったので今回アップデートが入って非常に嬉しいです！
vagrantのssh-configをchef-runで使う

vagrantのssh-configをknife-soloで使う場合は、以下のようにするのが一般的と思われる。

```sh
OMNIBUS_URL="https://omnitruck.chef.io/install.sh"
vagrant ssh-config --host "${HOSTNAME}" > "${SSH_CONFIG_NAME}"
knife solo prepare -F "${DIR}/${SSH_CONFIG_NAME}" "${HOSTNAME}" --omnibus-url "${OMNIBUS_URL}"
knife solo cook -F "${DIR}/${SSH_CONFIG_NAME}" "${HOSTNAME}"
```

が、chef-runは-Fをサポートしていない。以下のようにすることで-Fに相当する機能を実現することができる。

```sh
echo "
require 'net/ssh'
Net::SSH::Config.class_variable_set(:@@default_files, [(File.dirname File.realpath __FILE__) + '/${SSH_CONFIG_NAME}'])
log.level = :debug
log.target_level = :debug
# log.location = 'stdout'
" > "${DIR}/${SSH_CONFIG_NAME}.rb"

chef-run --chef-license accept -c "${DIR}/${SSH_CONFIG_NAME}.rb" "${HOSTNAME}" ${DIR}/cookbooks/${HOSTNAME}/recipes/default.rb
```

(`log.level = :debug`はなくてもよいが、どこで失敗したのかを~/.chef-workstation/logs/default.logに書き出してくれるので有用かと。というかknife-soloと違って進捗表示は出せないのかな。。)

### おわりに

`Net::SSH::Config.class_variable_set`の時点でわかると思いますが、本記事はまさかりを投げられることを期待されています。。
Node.js で osm-pbf-parser を使うときのメモ

# はじめに

Node.js で OpenStreetMap の PBF をパースするときに [osm-pbf-parser](https://www.npmjs.com/package/osm-pbf-parser) を使うことが多いのですが、example の書き方が 2015年当時のものでちょっと扱いに困ることがあるので、2022年ならこう書くかな？というものを記録しておきます。

# Before (official example)


```js
var fs = require('fs');
var through = require('through2');
var parseOSM = require('osm-pbf-parser');
 
var osm = parseOSM();
fs.createReadStream(process.argv[2])
    .pipe(osm)
    .pipe(through.obj(function (items, enc, next) {
        items.forEach(function (item) {
            console.log('item=', item);
        });
        next();
    }))
;
```

# After


```js
const fs = require("fs");
const Transform = require("stream").Transform;
const parseOSM = require('osm-pbf-parser');

function parse(input) {
  return input.pipe(parseOSM())
    .pipe(new Transform({
      objectMode: true,
      transform(items, enc, next) {
        for (const item of items)
          this.push(item);
        next();
      }
    }));
}

(async function() {
  for await (const item of parse(fs.createReadStream(process.argv[2]))) {
    console.log(item);
  }
})();

```

# まとめ

たまに思い出したように osm-pbf-parser を使うことがあるのですが、そのたびに through2 依存やめたい、とか、 イベントベースのコード書きたくない、と思いつつ放置していました。

[through2](https://www.npmjs.com/package/through2) は公式が言うように [stream.Transform](https://nodejs.org/api/stream.html#class-streamtransform) で代替可能。また、イベントベースのコードも Node.js v10 あたりで導入された、[readable の asyncIterator](https://nodejs.org/api/stream.html#readablesymbolasynciterator) を使うことで for await でさっぱりと書くことができるようになりました。
General Requirements on SPAL,  No.9, AUTOSAR R21-11(88) CP

General Requirements on SPAL,  No.9, 2021-11

https://www.autosar.org/fileadmin/standards/classic/21-11/AUTOSAR_SRS_SPALGeneral.pdf


AUTOSAR R21-11記事一覧はこちら。

AUTOSAR 21-11, 84文書読んだ。2022年6月中に半分到達予定。

https://qiita.com/kaizen_nagoya/items/9935c9e58bf82e1e63d2

AUTOSAR 21-11, 62文書読んだ。2022年5月中に半分到達予定。

https://qiita.com/kaizen_nagoya/items/648b32867635993410c8

AUTOSAR R21-11(0) 仕様ダウンロード一覧。単語帳。参考文献資料作成

https://qiita.com/kaizen_nagoya/items/9b3a1b9b8d1e8d7e288e

# 用語(terms)
Acronym: Description:
CS Chip Select
DIO Digital Input Output
ECU Electric Control Unit
ICU Interrupt Capture Unit
MAL Old name of Microcontroller Abstraction Layer (replaced by MCAL
because ‘MAL’ is a French term meaning ‘bad’)
MCAL Microcontroller Abstraction Layer
MCU Microcontroller Unit
MMU Memory Management Unit
Master A device controlling other devices (slaves, see below)
Slave A device being completely controlled by a master device
NMI Non Maskable Interrupt
OS Operating System
OCU Output Compare Unit
PLL Phase Locked Loop
PWM Pulse Width Modulation
RX Reception (in the context of bus communication)
SPAL Standard Peripheral Abstraction Layer (The name of this working
group)
SFR Special Function Register
RTE Runtime Environment
WP Work Package
Acronym: Description:
STD Standard
REQ Requirement
UNINIT Uninitialized (= not initialized)


## 英日
日本語は仮訳

T.B.D.

# 参考(reference)

[1] Glossary
AUTOSAR_TR_Glossary.pdf
[2] Layered Software Architecture
AUTOSAR_EXP_LayeredSoftwareArchitecture.pdf
[3] General Requirements on Basic Software Modules
AUTOSAR_SRS_BSWGeneral.pdf
[4] Specification of ECU State Manager
AUTOSAR_SWS_ECUStateManager.pdf
[5] Software Standardization Template
AUTOSAR_TPS_StandardizationTemplate.pdf
viteでlessをimportした際の inline JavaScript is not enabled. Is it set in your options? を解消する

viteでAntDesignのテーマのカスタマイズを行おうとしたときに 

`inline JavaScript is not enabled. Is it set in your options?`

というエラーが表示されました。

調べてみると  preprocessorのオプションに　`javascriptEnabled: true` を設定してあげないとダメそうです。

## `vite.config.js` の設定変更

というわけで件の設定はviteでどうやってやるのって調べたら以下のように書けばいけるみたいでした 

```js:vite.config.js
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), tsconfigPaths()],
  server: {
    host: true,
  },
  // ↓を追加する
  css: {
    preprocessorOptions: {
      less: {
        javascriptEnabled: true,
      },
    },
  },
});
```
Linuxで強制再起動する方法まとめ

## はじめに
学習をぶん回していると時々フリーズしてしまいますよね．
そんな時におススメの方法です．

## Linux再起動方法の3選

#### ①とりあえずコンソール切り替え
仮想コンソール(シェルプロンプト)に入って再起動コマンドをまず打ってみます．
`Ctrl` + `Alt` + `F[2~6]` で仮想コンソールに入り...

```python:config.py
# reboot
```
でOKです．

#### ②仮想コンソールに入れなくてもキーで再起動
①と同じくらい安全な方法に，
`Ctrl` + `Alt` + `Del` での再起動もあります． 

#### ③電源オフの前の最終手段
ゆっくりと...
`Alt` + `SysRq(PrintScreen)`を押しながら， `r`,`e`,`i`,`s`,`u`,`b`を押します．
絶対に安全な方法ではないらしいので，これでうまく再起動できたらもう一度正しい方法での再起動をすることをお勧めします．
それぞれの意味は[こちら](https://www.exceedsystem.net/2020/12/16/how-to-properly-reboot-computer-more-safely-when-linux-suddenly-stopped/#:~:text=Alt%20%2B%20SysRq(PrintScreen)%20%E3%82%92,%E3%82%A2%E3%83%AB%E3%83%95%E3%82%A1%E3%83%99%E3%83%83%E3%83%88%E3%82%AD%E3%83%BC%E3%82%92%E6%8A%BC%E4%B8%8B%E3%81%99%E3%82%8B%E3%80%82)を参照してください．


## おわりに
私の場合だと①②がダメでも③で大抵解決します．（一度スゴイ時差で再起動したこともあったので③をやってダメでもしばらく待っても良いかもしれません）

また，そもそもフリーズしないための手段としては，もしdockerを使用して学習している場合はコンテナを立ち上げる時に使用できるコアの数を制限するなどした方が良いかもしれません．

みなさまが魔の電源ボタンに手を伸ばさずに済みますように．
MC/DC のプログラミング具現ロージック


MCDCの具現は手作りのロージックはよく知られてるんですが、これをプログラミングで実現しようとしたらなかなかできません。これのプログラミングのロージックを以下で整理して見ました。



まずMCDCの基本単体になるのはANDとORです。

A and B のMCDC結果

|  A   |  B   | Results |
| :--: | :--: | :-----: |
|  1   |  1   |    1    |
|  1   |  0   |    0    |
|  0   |  1   |    0    |



A or BのMCDC結果

|  A   |  B   | Results |
| :--: | :--: | :-----: |
|  1   |  0   |    1    |
|  0   |  1   |    1    |
|  0   |  0   |    0    |



これを基本として拡張するロージックを書きます。



### 1. A and (B or C) パッタン

Aのケースは

|  A   | Results |
| :--: | :-----: |
|  1   |    1    |
|  0   |    0    |

B or C のケースは

|  B   |  C   | Results |
| :--: | :--: | :-----: |
|  0   |  0   |    0    |
|  0   |  1   |    1    |
|  1   |  0   |    1    |

この二つのケースをand演算子で繋げます。

Aのケースの隣ででB and Cの結果がTrue(1)であるのを１個選んで置きます。B or C の二番目とか三番目どちでも良いです。

| A    |  B   |  C   | Results |
| :----: | :--: | :--: | :-------: |
| 1    |  0   |  1   | 1       |
| 0    |  0   |  1   | 0       |

次の行ではAがTrue(1)のケース＋B or CがFalse(0)の場合を置きます。

|  A   |  B   |  C   | Results |
| :--: | :--: | :--: | :-----: |
|  1   |  0   |  1   |    1    |
|  0   |  0   |  1   |    0    |
|  1   |  0   |  0   |    0    |

ここまで残ったB or Cケースは下のようです。

|   B   |   C   | Results |
| :---: | :---: | :-----: |
| ~~0~~ | ~~0~~ |  ~~0~~  |
| ~~0~~ | ~~1~~ |  ~~0~~  |
|   1   |   0   |    0    |

続けてAがTrue(1)のケース＋残ったB or Cケースを合わせて追加します。B or Cの残ったのは三番目なので追加された結果は下のようです。

|   A   |   B   |   C   | Results |
| :---: | :---: | :---: | :-----: |
|   1   |   0   |   1   |    1    |
|   0   |   0   |   1   |    0    |
|   1   |   0   |   0   |    0    |
| **1** | **1** | **0** |  **1**  |



### 2. A or (B and C) パタン

Aのケースは

|  A   | Results |
| :--: | :-----: |
|  1   |    1    |
|  0   |    0    |

B and C のケースは

|  B   |  C   | Results |
| :--: | :--: | :-----: |
|  1   |  0   |    0    |
|  0   |  1   |    0    |
|  1   |  1   |    1    |

この二つのケースをor演算子で繋げます。

Aのケースの隣ででB and Cの結果がFalse(0)であるのを１個選んで置きます。この例えではB and C の一番目を選びました。

|  A   |  B   |  C   | Results |
| :--: | :--: | :--: | :-----: |
|  1   |  1   |  0   |    1    |
|  0   |  1   |  0   |    0    |

次の行ではAがFalse(0)のケース＋B and CがTrue(0)の場合を１個選んで置きます。

|  A   |  B   |  C   | Results |
| :--: | :--: | :--: | :-----: |
|  1   |  1   |  0   |    1    |
|  0   |  1   |  0   |    0    |
|  0   |  1   |  1   |    1    |

B and C の状態は下のようです。

|   B   |   C   | Results |
| :---: | :---: | :-----: |
| ~~1~~ | ~~0~~ |  ~~0~~  |
|   0   |   1   |    0    |
| ~~1~~ | ~~1~~ |  ~~1~~  |

続けてAがFalse(0)のケース＋残ったB or Cケースを合わせて追加します。B and Cの残ったのは二番目なので追加された結果は下のようです。

|  A   |  B   |  C   | Results |
| :--: | :--: | :--: | :-----: |
|  1   |  1   |  0   |    1    |
|  0   |  1   |  0   |    0    |
|  0   |  1   |  1   |    1    |
|  0   |  0   |  1   |    0    |



1,2のバタンを用いたら(A or B) and (C and D)とかに広がるのができます。手描きの結果とはビッタリ会わないけれどもMC/DCのカバレッジは全部カバーするので問題はありません。
M1 MacにNode.jsをインストールする方法と使い方（Node.jsバージョン管理)

## はじめに
nodenvを使うと、Node.jsのバージョン管理がしやすくなります。つまり、プロジェクト毎にNodeのバージョンを指定して実行ができます。

今回はM1 MACにHomebrewを使ったnodenvのインストール方法と使い方をこの記事でまとめていきたいと思います。

## 環境
・macOS Big Sur 11.4

## 1.Homebrewをインストール
[Homebrew](https://brew.sh/index_ja)をインストールしていない場合は、先にHomebrewをインストールします。
```
$　/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```

## 2.nodenvをインストール
nodenvをインストールします。
```
$　brew install nodenv
```

## 3.nodenv にパスを通す
自分は以下の記述がなかったため上手くバージョン管理ができなかったです。
```
export PATH="$HOME/.nodenv/bin:$PATH"
eval "$(nodenv init -)"
```
nodenvの設定が完了しているかどうかを確認します。以下のような結果になればOKです。

```
$ curl -fsSL https://github.com/nodenv/nodenv-installer/raw/master/bin/nodenv-doctor | bash
Checking for `nodenv' in PATH: /usr/local/bin/nodenv
Checking for nodenv shims in PATH: OK
Checking `nodenv install' support: /usr/local/bin/nodenv-install (node-build 4.9.43)
Counting installed Node versions: 1 versions
Auditing installed plugins: OK
```

ここまで設定できればターミナルを閉じて、再度開くとnodenvが使えるようになります。

## 指定のバージョンのNode.jsをインストールする
推奨版は[Node.js](https://nodejs.org/ja/)で常に更新されているため確認が必要です。

```
# インストール可能なNode.jsのバージョン一覧
$ nodenv install -l
...
16.13.2
16.14.0
16.14.1
16.14.2
16.15.0
...

# Node.jsの16.14.1と16.15.0をインストールする
$ nodenv install 16.14.1
$ nodenv install 16.15.0

# nodenvに認識させる
$ nodenv rehash

```

## Node.jsのバージョンを切り替える
```
# インストールされているNode.jsのバージョン一覧を確認
$ nodenv versions
  16.14.1
　　　　16.15.0

# ローカル（カレントディレクトリ配下）で利用するNode.jsのバージョンを設定する
$ nodenv local 16.14.1

# グローバル（システム全体）で利用するNode.jsのバージョンを設定する
$ nodenv global 16.14.1
```
PHPUnit x Laravel よく使うスニペット まとめ

laravel x PHPUnit で開発する際に忘れがちな処理をまとめました。

## リダイレクト先で assertSee
```php
$response = $this->post('users', [
    'name' => 'test_user',
]);

$response
    ->assertRedirect('/users')
    ->assertStatus(302);

$response->get('users')->assertSee('user一覧');
// or $this->followRedirects($response)->assertSee('user一覧');
```

## DB定義から factory を自動生成
https://github.com/TheDoctor0/laravel-factory-generator

```sh
composer require thedoctor0/laravel-factory-generator --dev

# --recursive で directory 構成に沿って生成してくれる
php artisan generate:factory --recursive 
```

## factory - 入れ子でまとめて生成
```php
$this->user = User::factory(1)
    ->create()
    ->each(function ($user) {
        Post::factory(1)->create([
            'user_id' => $user->id,
        ]);
    })[0];  // collectionなので、
```
```php
// Post が "user_id" を持っている場合は、 for でも行ける
$this->post = Post::factory()
    ->for(User::factory())
    ->create();
```


## 画像の保存処理
```php
$response = $this->post('/images', [
    'name' => 'sample',
    'image' => new \Illuminate\Http\UploadedFile(
        'path_to/test.png',
        'test.png',
        null,
        null,
        true,
    )
]);

$this->assertTrue(Storage::exists('path_to/test.png'));
```

```php
//  \Illuminate\Http\UploadedFile コンストラクターの定義
public function __construct(
    string $path,
    string $originalName,
    ?string $mimeType = null,
    ?int $error = null,
    bool $test = false
) { }
```
Teachable MachineとCodePenで資産管理用シールを迷わず見つけられるWEBアプリを作りました

こんにちは。こんばんは。
私は小売業で働いています。昨年秋に半期に一度実施する業務 **【資産棚卸】** を初めて実施しました。資産棚卸は、 **資産管理台帳に登録されている資産と、店で所有している資産が一致しているかどうかを確認する業務** です。

資産１つにつき１枚の管理用バーコードシールが貼られています。
![管理シール２.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2620758/198812ef-6af5-fe35-e45f-72fe50c93570.png)
この管理用バーコードシールをスキャンすると台帳上で「資産有り」と記録されます。

店の資産となる代表的なものは、
 * レジ関連機器
 * 商品陳列台
 * 冷蔵庫、冷凍庫
 * 調理関連機器
 * パソコン関連機器
 * 空調設備関連機器

他にも細かいものはありますが、これらすべてのバーコードシールをスキャンしなければなりません・・・。
 # 作成したWEBアプリ
資産名を表示すると管理用バーコードシールの場所を教えてくれる「資産検索」WEBアプリを作成しました。
![20220524_131212.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2620758/9777a19d-d389-293b-a16b-5852f39321c4.gif)
 # 資産管理用バーコードシールを探せ！
資産の中には本体に直接シールを貼ることができない場合があります。この **資産そのものに貼られていないシールを見つけるのがとっっっても大変** です。なぜならそのシールを貼る時に、貼る人がわかる場所に貼っているからです。もっと言うと **どこに貼ってあるかはそのシールを貼った本人しか分からない** のです。
　
<dt>機器そのものが高温になる場合</dt>
  <dd>揚げ物をする<strong>フライヤー</strong>や<strong>蒸し器</strong>などは本体が高温になるため機器近くの壁に貼ります</dd>
　
<dt>高所に設置されている場合</dt>
  <dd>天井に設置されている<strong>エアコン</strong>や<strong>店内安全カメラ</strong>は機器近くの壁や機器の操作室の壁に貼ります</dd>

 # 使用したツール
 * [Teachable Machine](https://teachablemachine.withgoogle.com/)
 * [CodePen](https://codepen.io/)

 # 資産名を学習させる
Teachable Machineを使って資産の場所を学習させます。昨年実施した時に特に見つけるのが難しかった５か所を学習させました。
![ティーチャブルマシーン差し替え用.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2620758/38a23074-d95b-720c-b399-f4ffcf4a1dec.png)

字体は３種類用意し、様々な角度から資産名を撮影しました。
（この時、どうしてもカメラが反映せずプレビュー表示ができませんでした。）

 # 学習モデルをCodePenでWEBアプリケーションにする
Teachable Machineで学習させたモデルをエクスポートします。
![ティーチャブルマシーン３.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2620758/ec1f42d1-9e20-0468-93cd-8341b112bc26.png)
モデルをアップロード後、表示されたモデルを使用するコードスニペットのp5.jsからコードをコピーしてCodePenのHTMLに貼り付けます。
![コードペン１.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2620758/f2b9daf8-c7d0-8287-8ad2-80e0a5bb0246.png)
CSSでデザインを編集をすることができます。背景色は一番好きな黄色にしました。
```css:CSS
body {
  background-color: #FFFF33;
}
```
タイトルは黄色に合う濃青にしました。
```css:CSS
div {
  color: #000080;
}
```
色を示す＃と６ケタの数字は「CSS　色」で検索するとたくさん出てきます。

画面右上のビューの変更から全ページ表示に切り替えます。
![コードペン２.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2620758/0d9ff2b3-8ef3-914b-51bb-021851251ca5.png)
表示されるURLにスマホからアクセスすると、スマホでも資産管理が使えるようになります。

 # 今後の課題
現時点ではスマホで使用する時にインカメラになっており、 **リアカメラに切り替える方法** を見つけられていません。リストから資産名を表示して検索するため、リアカメラに切り替える方法を勉強します。
今回は文章でシールの場所を表示していますが **実際に貼られている場所の写真も表示できるともっと見つけやすい** と思いました。

# あとがき
この仕組みは初めて資産棚卸をする時にはとても有効だと感じています。

私が一番衝撃的だったのは、電話機です。 **事務所にある固定電話機３台も資産登録** されていて、そのうち１台にはシールが貼られているのですが **残り２台にはシールが貼られていません** でした。事務所内のどこかにあるはず・・・と思いあちこち探しましたが見つけられず、いろんな人に聞きまわった結果、 **倉庫として活用している部屋にシールがありました。** 

なぜ倉庫に・・・
目の前に３台電話機があるのにバーコードシールが見つけられないために延々探し回ったのに、倉庫とは・・・

私の後任者には同じ思いはさせません！

最後まで読んでいただき、ありがとうございます。
　
　

 **５月２４日更新** 
 * CodePenの表記を修正しました
 * 縦長画像の不要部分を削除しました
【AtCoder】ABC251 D問題 解説

# 問題

$3$ つ以下その中から選んで和をとるとき、$1 ～W$ までの数を全て作れるような、要素数 $300$ 以下の集合を作成する問題。
サービス問題のようで全くサービス問題ではない問題。

https://atcoder.jp/contests/abc251/tasks/abc251_d

# 初めに考えたこと(誤った方針)

$1$ から順番に数を見ていって、以下の操作を行う。
始め集合 $S$ は空集合としておく。みている数字を $i$ とする。

-  $i+1$ から $i+i-1$ まで数を見ていく。
- $j$ を作るのにすでに $3$ つの要素を使ってしまっているなら、集合 $S$ に $i+j$ を追加する。ここが呼び出さたなら、ループを終了し、$i$ を $i+j$ と改めてループを開始する。
- $i$ が $W$ をこえたら終了。

$1$ つの数に対しては $1$ 回の条件分岐が発生するので、計算量としては $O(W)$ となって間に合う。

ところが、この解法は要素数 $300$ という制約を完全に無視した方針となっている。
そして、案の定というか、$W$ が1000を超えたあたりですでに要素数が $300$ を上回っていた。

なんでこんなことをしたかというと、『この解法が模範解答に違いない！制約も自動的に追いついてくれる！』と思い込んでいたというのがある。
こういった支離滅裂で崩壊した思考をする癖は、将来仕事についたときに足を引っ張るものだと思うので、これを機にやめたい。

# 次に考えたこと (誤った方針2)(すでに残り時間10分)

さて、上記の思考と実装で三十分近く時間を無駄にした。
B問題で $map$ の計算量の重さを知らず、TLEで四十分近く悩んでいたというのもあり、残り時間は十分くらいしかなかった。

ここでふと脳裏に以下の方針を思いついた。
- $W$ をちょうど超える $2^n$を見つける
- 1,10,100,1000,みたなものを用意していけば、いけるのでは...

近からず遠からずみたいな発想ではあったが、結果的に実装すら間に合わず終了。
結局コンテストの流れとしては、

- D(絶望)→C→B(mapの仕様を知らず、40分程度悩む)→A→D(希望がみえつつ実装が追い付かないことを悟る)→＼(^o^)／

という悲しい感じで終了した。
結果的にEから始めていれば...という感じ。結局E問題は読むことすらなく終了した。
パフォーマンスも過去最低値を記録し、若干モチベが低下した(その後回復した)。

# 模範解答

これ以上にスッキリする答えはないだろう、というくらい、過去一『あ～なるほどね！』となった問題だ。

『要素数 $300$』という制約と、『$10^6$』というのがヒントだった。

要するに桁を一と十の位、百と千のくらい、万と十万のくらいに分けて考えればよい。
そうすれば、$1～99(1とばし),100～9900(100とばし),10000～990000(10000とばし)$ という $297$ 個の数を用意するだけで$ 000001～999999$が全て作成できる。
これに $1000000$ を追加した $298$ 個の要素で、どんな $W$ に対しても対応できることになる。

プログラミングというよりただ発想の問題だった。完全に意表をつかれたので、こういった問題の対策もしていきい。

# 実装


```cpp:ABC251_D.cpp
int main() {

	int w; cin >> w;
	cout << 298 << endl;
 
	repa(i, 99) cout << i << " ";
	for (int i = 100; i < 10000; i += 100) cout << i << " ";
	for (int i = 10000; i < 1000000; i += 10000) cout << i << " ";
	cout << 1000000 << endl;
 
	return 0;
}
```
BOOL expressionの引数は文字列である

```cmake
project(cmake-string)
set(FOO 0)

add_library(foobar SHARED a.c)
target_link_libraries(foobar $<IF:$<BOOL:FOO>,bar,baz>)
add_library(foobaz SHARED a.c)
target_link_libraries(foobaz $<IF:$<BOOL:${FOO}>,bar,baz>)
```

FOO=0なのでIF expressionはFALSEを期待していますが、BOOL expressionは文字列を引数に取るので、
上の例だと誤ってlibbarを参照してしまいます。
ReactのuseEffectをしっかり理解したい

useEffectはよく使うhookですが、正しく使えているのが不安になったので、調べてみました。

# useEffectについて
React16.8でフックという機能が追加され、useEffectというフックが使えるようになった。
この新機能によって、stateの機能などが関数コンポーネントでも使えるようになった。
useEffectフックはcomponentDidMountとcomponentDidUpdateとcomponentWillUnmountがまとまったもの

### useEffectがやっていること
React がコンポーネントをレンダーする際にReactは副作用を覚えておき、DOMを更新した後に呼び出す。
1. レンダー後に何かの処理をしないといけない、ということをReactに伝える
1. Reactは渡された関数を覚える
1. DOMの更新の後にその関数を呼び出す

##### 「レンダーの後」に副作用は起こる
デフォルトでは、副作用関数は初回のレンダー時および毎回の更新時に呼び出される。
Reactは、副作用が実行される時点ではDOMが正しく更新され終わっていることを保証する。
##### レンダーごとに渡される関数は異なる
再レンダーごとに、React は違う副作用関数をスケジュールし、前のものを置き換えるため、古い値を参照してしまう心配なしに関数を実行することができる。
副作用は特定の1回のレンダーと結びついている。

# クリーンアップを必要としない副作用
ReactがDOMを更新した後で追加のコードを実行したい時。例えば、
- ネットワークリクエストの送信
- 手動でのDOM改変
- ログの記録

コードが実行されたあとすぐにその関数のことを忘れても構わないため。（継続しない処理）

# クリーンアップを有する副作用
外部のデータソースへの購読をセットアップしたい時などは、メモリリークが発生しないようにクリーンアップが必要。
その他マウント中に継続するような処理setIntervalなど。

### クリーンアップの例
```react:
import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
```
### クリーンアップ処理の特徴
##### useEffect関数内に書く
クリーンアップ用の副作用は必要なく、useEffect内に一緒に書くことができる。
##### 実行されるタイミング
1. コンポーネントのアンマウント時
1. 毎回のレンダー時（副作用関数が実行される前）
##### スキップによるパフォーマンスの改善
副作用のクリーンアップと関数の処理をレンダーごとに毎回行うとパフォーマンスの問題を引き起こす可能性がある。
第２引数に配列を渡すことで、その配列内の要素に変更がない場合は処理をスキップする。
配列に複数の要素がある場合は一つでも変わっている場合は処理を実行する。
例では、第２引数に[count]を渡している。
```react
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
```
第２引数に[]空配列を渡すと、副作用内ではpropsとStateは初期値のままになる。propsやStateに依存しないので、再実行は発生せず、レンダー時に実行されるのみとなる。


# 参考
https://ja.reactjs.org/docs/hooks-effect.html

https://takamints.hatenablog.jp/entry/cleanup-an-async-use-effect-hook-of-react-function-componet
C言語のポインタ理解できた記念日

# ポインタ変数

![pointer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659122/63860136-4103-9a80-ea49-77489eef768b.png)
```c
char *x = "hello";
```

ポインタ変数`*x`は、`hello\0`が格納された場所の**アドレス**を持っている。

ポインタ変数`*x`が格納してるのは、値そのもの（ここでは`hello\0`）ではなく、（値を格納しているメモリ上の）”アドレス”である。

# アドレス演算子

`&x`とは、変数`x`のアドレスのこと。

`&x`は変数`x`のアドレスなのだから

```c
int *x_ptr = &x;
```

とすると、ポインタ変数`x_ptr`に変数`x`のアドレスを直接代入できる。

これで、ポインタ変数`x_ptr`に変数`x`のアドレスを格納できた。`x_ptr`は変数`x`をポイントしてる（指し示してる）。

# 間接演算子

```
*ポインタ変数
```

というフォーマット。

```c
int32_t a = 8;
int32_t *a_ptr = &a;
printf("%i", *a_ptr);  // => 8
                       // ここの *a_ptr が間接演算子
```
間接演算子`*`をポインタ変数`a_ptr`に適用すると、`a_ptr`が指し示しているアドレスにある値を取り出す。

`*a_ptr`とは、ポインタ変数`a_ptr`がポイントしてるとこにある値なのだから、

```c
*a_ptr = 99;
printf("%i", a);  // =>99
```

のように、代入（上書き）ができる。

ちなみに、最後の`printf`は間接演算子を使って

```c
printf("%i", *a_ptr);
```
でも同じ結果。

`int32_t *a_ptr = &a;`を省略することもできる：

```c
int32_t a = 8;
printf("%i", *&a);
```

（`&a`に対して直接、間接演算子`*`を適用してる）

# 配列とポインタ

```c
#include <stdio.h>

int main()
{
	int32_t a[] = {123, 4545, 999};
	int32_t *a_ptr = a;  // 配列名を直接ポインタ変数に与えると、その配列の先頭要素のアドレスが得られる（アドレス演算子は不要）
	printf("%i\n", *a_ptr);  // 間接演算子

	a_ptr = &a[1];  // 配列中の個々の要素に対しては、アドレス演算子が必要
	printf("%i", *a_ptr);  // 間接演算子
}
```

```bash
$ ./a.out
123
4545
```

# ポインタとインデックス

ポインタ変数`p`に対して、`p[i]`は間接演算子`*(p + i)`に等しい。

```c
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

int main()
{
	int32_t array[] = {1, 2, 3, 4};
	int32_t *p = array;

	for (size_t i = 0; i < sizeof array / sizeof(int32_t); i++) {
		printf("%i\n", p[i]);  // p[i] is *(p + i)
	}
}
```

実行すると

```
$ ./a.out
1
2
3
4
```

# 関数ポインタ

**関数のアドレスを格納したポインタ変数**が「関数ポインタ」。

## 関数ポインタ変数の宣言

```
返り値の型 (*ポインタ変数名)(パラメータリスト)
```

`compare`関数へのポインタ変数`p`を定義するには

```c
int compare(int x, int y);

int (*p)(int, int) = compre;  // 「*p = &x」と対応
```

最後の行にあるように、関数のアドレスを渡す際は関数名を与える。~~&compareとかcompare()じゃない。~~

`&compare`でも可ということでした（コメントより）。

パラメータリストの部分は型だけでよい（つまり`(*p)(int x, int y)`と書かなくても大丈夫）。

## 関数ポインタの間接演算子

```
(*ポインタ変数名)(パラメータリスト)
```
または

```
ポインタ変数名(パラメータリスト)
```

`compare`関数のポインタ変数を定義して、間接演算子で関数を呼び出す例：

```c
int compare(int x, int y);
int (*p)(int, int) = compare;  // 関数compareへのポインタ変数p

// 下の二行は同じ結果になる
printf("func result: %i", (*p)(3, 4));
printf("func result: %i", p(3, 4));  
```

## `typedef`と関数ポインタ

```c
int (*fptr1)(int);

int square(int num) {
  return num * num;
}

int n = 5;
fptr1 = square;
printf("squared: %d", fptr1(n));
```

`typedef`を使って書くと

```c
typedef int (*funcptr)(int);  // funcptr型が使えるようになる
funcptr ftp2;
ftpr2 = square;
printf("squared: %d", fptr2(n));
```
オブジェクト指向・設計用語 備忘録



# はじめに
<!-- 発端や概要を記載 -->
自身の<strong>設計力、良いコードを書く力を上げるべく</strong>、勉強した内容を更新していきます。
メモ的な使い方にはなりますが、用語集の中でわからない用語などあれば、<strong>説明を読む OR 調べる(参考文献を読む)</strong>をしていただくと、
読者様の<strong>設計力向上</strong>につながるかと思います。

特に、<strong>初心者の方におススメ</strong>です。

私自身、未経験からエンジニアとなり、
<strong>コードを書く上で自分のコードが良いのか悪いのか、もっといい書き方があるのかなど悩んできた</strong>ので、
同じ悩みを持つ人の助けになれば幸いです。

# 目次
<!-- タイトルとアンカー名を編集 -->
1. [用語集](#Chapter1)
1. [参考書籍一覧](#Chapter2)

<!-- 各チャプター -->
<a id="#Chapter1"></a>
# 用語集
・出力引数
　メソッドに参照渡しとして引数に渡して、メソッド内でパラメータを変更する。
　呼び出した元では、メソッドの戻り値を受け取るわけではないが、
　渡したオブジェクトを介して、何らかの処理結果を受け取ることができる。
　オブジェクト指向的にはアンチパターン。副作用があるので。

<a id="#Chapter2"></a>
# 参考書籍一覧
- [現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法](https://amzn.to/39MzZla)
- [良いコード／悪いコードで学ぶ設計入門―保守しやすい　成長し続けるコードの書き方](https://amzn.to/3lwK60g)