OmniAICreator/Qiita-1.07M huggingface dataset:
AI-102合格記録

この度AI-102に合格したため、こちらに記録として残します。ご覧になった方の参考になれば幸いです。

# 受けたきっかけ
前回G検定を受け、次のステップとして受けることを決めました。

# 勉強スケジュール
AI-102を受けるにあたって、2ヶ月ほど要しました。テキスト学習は行わず、問題演習のみ実施しております。また、AI-102の事前準備としてAI-900を受験しました。
G検定の前提知識があるため、1から学習される方はもう少し時間を取っても良いかと思います。(テキスト学習などを追加して+1ヶ月など)

教材はUdemyの模擬問題集とMicrosoft公式のトレーニングを使用しました。

# 結果
結果は合格しました。
点数も700点後半(1000点満点中)と合格点の700点を少し上回る結果でした。

# 今回の受験での反省
### 良かったこと
- 公式トレーニングの活用
- 通勤時間を活用して、問題演習に取り組んだ
- G検定とAI-900の勉強を経ているため、前提知識が身についていた

### 悪かったこと
- 仕事とプライベートともに忙しい時期と重なった

Azure資格を受ける際、公式が提供しているトレーニング、模擬試験は非常に役立つコンテンツでした。
公式が提供している点は、正確性もあり「これを勉強すれば間違いない」という安心感もあります。さらに無料で利用可能ですので、使わない手はないです。私はビビりなのでラボを使った学習はしませんでしたが、慣れている方はラボでの学習も非常に役立つと思います。

# 最後に
G検定とAI-900を順に受けたため、AI技術の用語やAzureのサービスはある程度理解している状態で学習ができました。難易度としてはG検定と同等くらいかと思いますので、製品に偏りなくAIの知識がある方はG検定を、Azureの使用経験がある方はAI-102を受けるという順番も良いかと思います。
また、時間に余裕のある方、着実にステップアップしたい方はAI-900をそれぞれの資格を受ける前に受験されるほうが良いと思います。

AI-102に関する情報、教材は下記のURLをご参考ください。AI-900についても少し載せております。
[AI-102の公式ページ](https://learn.microsoft.com/ja-jp/credentials/certifications/azure-ai-engineer/?practice-assessment-type=certification "AI-102の公式ページ")
[AI-900の公式ページ](https://learn.microsoft.com/ja-jp/credentials/certifications/azure-ai-fundamentals/?practice-assessment-type=certification "AI-900の公式ページ")
[Practice Tests for AI102(udemy問題集)](https://www.udemy.com/course/practice-tests-for-ai102-azure-ai-engineer-certification/?couponCode=ST11MT170325G2 "Practice Tests for AI102(udemy問題集)")

:::note warn
注意
AI系の資格はシラバスの更新頻度が多い場合もありますので、教材は最新のもの、更新のあるものが良いです。
筆者が使用したudemyの問題集は英語ですので、ブラウザの翻訳機能等で適宜日本語に読み替える必要があります。
:::

最後までお読みいただきありがとうございました。
Flutterのimage_pickerで選択された画像が同一のファイルか判定する

担当しているFlutter案件で「ユーザーが同じ画像を選択できないようにしたい」という要望があったため、それに対応するまでに試したことを共有します。

# 開発環境
Flutter 3.19.6
image_picker 1.1.0

# ファイルパスが同一かで判定してみる
最初に思いついた方法はファイルパスでの比較でした。

```dart
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
print('path: ${image?.path}');
```

[pickImage()](https://pub.dev/documentation/image_picker/latest/image_picker/ImagePicker/pickImage.html)の戻り値でローカルストレージに保存されたファイル情報が取得できるため、その保存パスを比較することで同一ファイル判定ができないか試してみました。

```:Android
path: /data/user/0/your.package.name/cache/scaled_sample.jpg // 1枚目
path: /data/user/0/your.package.name/cache/scaled_sample.jpg // 2枚目
```

```:iOS
path: /private/<省略>/image_picker_339FE219-8B2F-454D-A063-67B92BB33E22-21767-000008C3A666E13C.jpg // 1枚目
path: /private/<省略>/image_picker_159530BA-FCC3-4396-A769-9154228F8D95-21767-000008C4043B17AE.jpg // 2枚目
```

実験してみた結果、Androidでは同じ画像を何度選択しても同一のパスになりましたが、iOSでは選択するたびにパスのファイル名部分が変化してしまい、この方法では同一ファイル判定ができないとわかりました。

:::note info
こういった比較方法は、利用しているパッケージ側で仕様変更が発生すると機能しなくなる可能性もあるため、なるべく避けた方が良いです。
::::

# ファイルのハッシュ値が同一かで判定してみる
ファイルパス以外の方法としてファイルサイズによる判定も検討してみたのですが、今回はもう少し精度を上げたかったので、最終的にはファイルのハッシュ値（SHA-256）を使った方法に落ち着きました。

ハッシュ値の生成には[crypto](https://pub.dev/packages/crypto)パッケージを使います。

```dart
import 'package:crypto/crypto.dart';

final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
  final Uint8List imageBytes = await File(image.path).readAsBytes();
  final String hash = sha256.convert(imageBytes).toString();
}
```

この方法で今回の要望は満たせたのですが、以下の懸念点があるため、これらを企画者に説明し、納得して貰った上での採用となりました。

> 1. 名前だけを変えたファイルは同一ファイルとして判定される
> 2. 通常の処理に加えてバイトデータを取得しているため、メモリ負荷が上がる

1はサービスの仕様によって良い悪いの判断が分かれるところだと思いますが、2は明確な悪影響になります。

個人的には、同一ファイルを弾くためだけに今回の処理を行うのはやり過ぎな気がするため、仕様の調整で済むのであれば、そちらの方が良いと思います。

# まとめ
ハッシュ値を作成することで同一ファイル判定ができるようになりました。

今回は画像データの取得を例にあげましたが、動画やその他のファイルでも使える方法なので、機会があれば一度試してみてください。
\
\
株式会社ボトルキューブではFlutterを使ったお仕事を募集中です。
お問い合わせは下記リンク先のフォームからご連絡ください。

https://www.bottlecube.co.jp/contact
Google Classroom APIで404エラーになったが解決方法が一向に分からなかった

# 起きたこと
Google Classroomのアドオンを開発していて、開発環境での動作確認も済んだので本番用のプロジェクトを作成して動作確認したところ、アドオンからの添付でエラーが発生しました。
> code\": 404, \"message\": \"Method not found.

# 調査したこと
### 状況① 開発環境ではうまくいっており、ソースコード自体は変更していない
前提として、環境変数で設定するOAUthクライアント等の値は正しく本番環境用に設定しています。

#### リクエストの詳細確認
まずは、添付時にどのようなリクエストを送っているか確認してみます。
APIのリクエストの形式は以下のようになっています。
`POST  https://classroom.googleapis.com/v1/courses/{courseId}/announcements/{itemId}/addOnAttachments`

https://developers.google.com/classroom/reference/rest/v1/courses.courseWork.addOnAttachments/create?hl=ja

ここで、`courseId`と `itemId`はクラスルーム内の課題固有の値であり、アドオンの種類によって変動するものではないため、この値が不正であるということではなさそうです。
クエリパラメータとして`addOnToken`がありますが、これはアドオンのiframeに渡される識別用のトークンで、この値が不正であるなら`403 @InvalidAddOnToken`がレスポンスされるため、この値は問題ないと判断しました。
また、APIのリクエストに必要なスコープが不足していることも考えられますが、その場合は401エラーがレスポンスされるため、それが原因ではなさそうです。

つまるところ、添付が成功するパターンと失敗するパターンでクリティカルなリクエストの違いはなさそうでした。

上記のような状況から、ソースコード自体に問題はなく、GCPのプロジェクトの設定に問題があると推測しました。

### 状況② 本番公開用と開発用（内部アプリ）の設定の違い
この場合、
- OAuthの対象ユーザーが「内部」か「外部」で違う
- Google Workspace Marketplace SDKでの「アプリの構成 > アプリの公開設定」での設定値が違う

などの差分があります。そのため、問題の切り分けのために、別の開発用のプロジェクトを作成して確認してみました。
しかし、変わらずエラーが出ています。設定内容を隅々まで確認してみましたが、特に気になる部分はありませんでした。

### 状況③ 組織の管理コンソールでのAPI許可設定
組織の管理者者アカウントで、管理コンソールの「セキュリティ > アクセスとデータ管理 > APIの制御 > Googleサービス」から、サードパーティーアプリに対しての各GoogleサービスAPIの呼び出しを管理できます。
このリストにアプリが表示されるのはどうやら承認の24～48時間後らしく、プロジェクト作成直後の確認ではうまくいかないものかと思い、48時間経過を待ちました。

https://support.google.com/a/answer/7281227?hl=ja

48時間後、アプリ一覧に表示され、APIへのアクセスも許可しましたが、状況は変わらずでした。

# 解決方法
上記のような状況から全く原因が分からず、DeepResearch等を使っても解決に至らなかったので手詰まりでした。
一から状況を見直し、アドオンのサンプルプロジェクトを確認したところ、添付でのAPIリクエストでGoogleの公式ライブラリを用いていたことが分かりました。そのため該当部分を公式のサンプルに合わせる形で修正したところ、うまくいきました。
```php:修正前
$client = new \GuzzleHttp\Client();
// 中略
$client->post($url, [
    'headers' => $headers,
    'json' => $body,
]);
```

```php:修正後
$service = new \Google\Service\Classroom($client);
$sub = new \Google\Service\Classroom\AddOnAttachment();
// 中略
$service->courses_courseWorkMaterials_addOnAttachments->create(
    $courseId,
    $itemId,
    $sub,
    ['addOnToken' => $addonToken]
);

```

なぜ修正後になるとうまくいくかは調査しきれていないですが（修正前でもリクエスト自体に問題ないはず・・・）、上記のようにすることで添付がうまく機能するようになりました。

# 学び
- 公式のサンプルがあるならそれに倣う
- 公式のライブラリがあるならそれを使う
- ソースコードに間違いはない、というバイアスを捨てる
    - ひととおり仮説を検証してみてそれでも原因が分からないなら原点に立ち戻ってソースコードを疑う
Raspberry Pi 400でUSB拡張

### ラズパイのUSBポートが少ない。
ってことありますよね？
今回その対策としてやってみたことがあるので自分のための備忘録として残します。
(設定方法のみが知りたい方は以下を飛ばしてください。)
# USBを使いたい
Pi 400のUSBは3ポートしかなく、SSD、マウスを繋いだらあと一つしかありません。
さらにそのポートを拡張しようとハブを接続して、使用すると電力不足で落ちます。(3.0)
こんな使い方をしていると、USBが使いにくいです。
WEBサーバーのように使用している方は問題ないと思います。しかしデスクトップとして使うとなると話は別です。
# どんな方法があるのか
別途電源が必要なハブなどもありますが、**電源供給用のUSB Type-C**を使うことにしました。
最近知ったもんで恥ずかしいですが、電源供給用以外の用途でも使えます。
# Type-Cから拡張すると良いところ
電源が直接ついているところにハブをつけるので、電源不足は基本的になくなります。さらにハブによってはいろいろな機能がついているのでPiがより使いやすくなります。

# 設定方法
/boot/firmware/config.txtをいじるだけです。
以下を追記します
```
dtoverlay=dwc2
modules-load=dwc2,g_serial
otg_mode=1
```
(一応cmdline.txtも乗せます)
```
console=serial0,115200 console=tty1 root=PARTUUID=3c9d9e8d-02 rootfstype=ext4 fsck.repair=yes rootwait modules-load=dwc2 quiet splash plymouth.ignore-serial-consoles
```
# ハブ
どれでもいいと思います。電源供給しながら使えるものならいいです。ただHDMIなどがついているのはその機能が使えません。USB2.0なのでUSBメモリなどに適さないですね。うまい使い方をすればいいと思います。
フローを作成しよう～リストビューから所有者を一括更新する方法

本橋孝昭です。

今回はリストビューから所有者一括更新するフローを作成します

通常リストビューからは一括でデータ更新が可能です

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/a0519f08-dd07-4cca-8737-d3c8349f4aa0.png)

しかし、所有者項目は特殊な項目であり鍵マークがかかっているのでリストビューから更新できません

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/0a02a58a-e291-458e-bddd-37d03a13ecf4.png)

今回はフローを作ることで、
右上に「所有者更新」ボタンを表示します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/7111a1e3-3b9e-4c9e-8629-6896b20b4738.png)

そして、ユーザーを選択して保存すると、リストビューから選択した商談レコードが一括で所有者更新がされます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/6137df29-85bd-45d5-962d-e3ae2a606e15.png)


今回のフローでは「商談とユーザーの参照関係のカスタム項目」のようなカスタム項目の作成は不要です。

では、どのようにしてフローを作成するのか見ていきましょう


フローの概要はこちらです


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/9c468b2d-44d0-4210-9426-a59b700d3847.png)

商談取得のidsはこちらです

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/31f4112d-b30f-42fb-8486-69b25f1e0ba0.png)


画面フローはこちらです

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/b34f7030-7617-4ff1-8c58-5ccb3baea147.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/fe96d524-e822-4e5d-b9f1-4da361c54c12.png)

商談一覧として表示される項目はこちらの並びとなります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/80ec545b-c16a-437f-95b9-1c6eae5acbd0.png)

最後の商談更新はこちらです

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/27acf314-5bb4-4a92-96d0-390d17e93c6c.png)

次にボタンを作成します

作成するボタンはこちらです

フロー名は「OpportunityOwnerChange」としました
/flow/OpportunityOwnerChange?retUrl=lightning/o/Opportunity/list?filterName=RecentlyViewedOpportunities

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/c59866ad-00d9-4bb3-bde7-c45f8fdc7cfe.png)



あとは作成したアクションをリストビューで表示させるだけです

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/09ab8621-8c94-42b3-82da-4b3f65c4cdcd.png)

以上でフローは完成です。お疲れ様でした。
個人的備忘録：Terraformがかっこいい理由について自分なりにまとめてみた

## はじめに
Terraformは、インフラをコードとして管理できる強力なツールです。

:::note info
**特にクラウド環境を扱うエンジニアにとって、シンプルでありながら高機能なこのツールは非常に魅力的です。**
:::

本記事では、Terraformがなぜかっこいいのか、その理由を解説していきます。

## 書こうと思ったきっかけ
近年、クラウドの活用が一般的になり、インフラの管理方法も大きく変わってきました。

![スクリーンショット 2024-10-26 10.53.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/a5a8b1d1-4124-35f2-8f4d-93ee265be040.png)
引用画像：https://cn.teldevice.co.jp/blog/p55426/

手作業での管理は非効率であり、エラーの原因にもなります。

:::note warn
***その中で、Terraformはインフラ管理を効率化し、チームでの運用をスムーズにするツールとして注目されています。***
:::

私自身もTerraformを使う機会が増え、その便利さとかっこよさを実感したため、この記事を書こうと思いました。

## 1. **コードでインフラを管理できる**
Terraformを使うことで、インフラ構築をコード（HCL: HashiCorp Configuration Language）として管理できます。

https://qiita.com/free-honda/items/682e29d92e60e2d17681

手作業での構築ではなく、コードを書いて適用するだけで、一貫性のある環境を作ることができます。

## 2. **マルチクラウド対応**
TerraformはAWS、GCP、Azure、その他多くのクラウドプロバイダーに対応しています。

https://qiita.com/free-honda/items/4cadfd1bafa6c71c85fb#terraform%E3%81%A8%E3%81%AF

クラウドごとに異なる管理ツールを使わなくても、Terraformだけで統一的に管理できるのが大きなメリットです。

## 3. **宣言的アプローチ**
Terraformは"何を作るか"を記述する宣言的なアプローチを採用しています。

Ansibleのような手続き的なツールと異なり、現在の状態をもとに必要な変更を適用するため、余計な手間がかかりません。

## 4. **State管理で一貫性を保つ**
Terraformは"tfstate"ファイルを使い、現在のインフラの状態を管理します。これにより、環境のズレを防ぎ、一貫性を保ったインフラ運用が可能になります。

https://qiita.com/free-honda/items/9df1f7b2663b8444bc1e

## 5. **コードレビューやバージョン管理ができる**
Terraformの設定ファイルはGitなどのバージョン管理システムと組み合わせることで、チームでのコードレビューや変更履歴の管理が容易になります。

## 6. **プラグインによる拡張性**
Terraformはプロバイダーごとにプラグインを提供しており、新しいサービスやツールにも柔軟に対応できます。例えば、Kubernetesの管理やDatadogとの統合も可能です。

https://qiita.com/free-honda/items/9e2b48903827df375c6d

## 7. **PlanとApplyで安全なデプロイ**
Terraformでは、実際に変更を適用する前に `terraform plan` を実行し、どのような変更が行われるかを事前に確認できます。

https://qiita.com/free-honda/items/bada92ba51364de03328

これにより、予期しない変更を防ぐことができます。

## 8. **インフラの再利用が容易**
Terraformにはモジュールという機能があり、共通の構成を再利用することができます。これにより、大規模な環境構築でも効率的に管理できます。

https://qiita.com/free-honda/items/1942df5e9dfb60a74c86

## 9. **オープンソースで活発なコミュニティ**
Terraformはオープンソースプロジェクトであり、世界中のエンジニアが改善や機能追加に貢献しています。公式ドキュメントも充実しており、困ったときに助けとなる情報が豊富です。

## 10. **スクリプト感覚でインフラを操れる**
Terraformのコードを書くことで、まるでプログラムを書くようにインフラを制御できます。

https://qiita.com/free-honda/items/d2a13fc94004c86d6d9c

CLIで `terraform apply` を実行すると、自動的に必要なリソースが作成されるのは非常に気持ちの良い瞬間です。

## まとめ

Terraformを使うことで、手作業のインフラ構築から解放され、シンプルかつ効率的にインフラ管理ができます。

:::note info
***特に、チーム開発や大規模環境での運用には欠かせないツールとなると思います！***
:::

Terraformを学ぶことで、よりモダンでスマートなインフラ管理を実現できます！
【Jenkins】Jenkinsfileの書き方について

## Jenkinsとは
とりあえず、CI/CDツールです。

## Jenkinsfileとはなに？

jenkinsの処理を単一のスクリプトファイルに記述したものになります。
ちなみに、こちらJenkinsfileはjenkinsパイプラインでしか使えません。

## Jenkinsfileには2つの書き方がある

Jenkinsfileの書き方には、**Declarative**という書き方と、**Scripted**という書き方があります。

2つの違いは開始タグが違うということです。

**Declarative**はpipelineで始まり、**Scripted**はnodeで記述が始まります。

ここでは、Declarativeの書き方柔軟で、公式ではDeclarativeを推奨しているため、Declarartiveを詳しく説明していきます。

また、JenkinsfileではGroovyの記述で処理を記載することができます。

### サンプルの記述方法

以下は **"test jenkins"** を出力するサンプルになります。

```
pipeline {
    agent any
    stages {
        stage("sample") {
            steps {
                sh 'echo "test jenkins"'
            }
        }
    }
}
```

## jenkinsのジョブ作成

1: まず、jenkinsにログインします
2: 赤枠部分、新規ジョブを作成します

![000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544972/fa3f9427-860e-43c9-9ffc-65c3939f0c6f.png)
3: 左メニューの赤枠のジョブ名を入力後**パイプライン**を選択し「OK」を押します

![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544972/f7226b2a-0b2d-4570-8821-617ca2d0adaa.png)

4: Genelal > パイプライン を選択すれば、定義の選択があるので、赤枠部分 **pipeline script from SCM** を選択します
![002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544972/19147215-5c43-4744-815e-d4e18de119f5.png)

5: SCM > gitを選択して、Jenkinsfileを管理しているgit情報を入力して「保存」を押します

リポジトリURLと認証情報を正しく入力します
githubなど設定した情報を入力します
![003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544972/8fb6bd63-d657-4b22-bcae-726babf30757.png)

ブランチ指定子を設定します。
**master**や**main,develop**などを指定します

![004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544972/7a7341f5-57d1-4969-8381-e35a2effdcb3.png)

ScriptPathは、**jenkinsfile**のある場所のパスを入力します

![005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544972/4fd7e037-32b8-4dea-acbf-81b8d78295dc.png)

6: job作成完了後

![006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544972/9adb05a2-2a12-443e-b540-f1697eb678f9.png)


7: 動作確認のためJenkinsの作成したジョブを**ビルド実行** から実行します

![007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544972/dfc325b3-24cb-458a-bcc2-f70ec118c31a.png)

![008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544972/30d4ba72-2e2b-4ffb-b0db-770adaf19c87.png)
Excel VBA PDFファイルテキストをエクセルシートに書き出す

### はじめに
PDFファイルテキストをエクセルシートに書き出しを行う

### プロシジャーの説明
指定のPDFファイルのテキストデータをエクセルシートへ書き出します。
PDFファイルをMicrosoft Wordで開き、その内容をExcelに転記するVBAマクロです。「Adobe Acrobat SDK」や「iTextSharp」など無しで動作します。

#### サンプル１ 
``` VB
Sub PDFToExcelImproved999()
    Dim wordApp As Object
    Dim wordDoc As Object
    Dim pdfPath As String
    Dim wordText As String
    Dim textLines As Variant
    Dim i As Long

    ' PDFファイルのパスを指定
    pdfPath = "C:\ALLlib\エクセルマクロ\test\AAA.pdf"

    ' Wordアプリケーションを作成
    On Error Resume Next
    Set wordApp = CreateObject("Word.Application")
    If wordApp Is Nothing Then
        MsgBox "Microsoft Wordが見つかりません。", vbExclamation
        Exit Sub
    End If
    On Error GoTo 0

    ' Wordを非表示で操作
    wordApp.Visible = False

    ' PDFファイルをWordで開く
    Set wordDoc = wordApp.Documents.Open(pdfPath)

    ' Word文書の内容を取得
    wordText = wordDoc.Content.Text

    ' 改行コードを考慮して分割
    If InStr(wordText, vbCrLf) > 0 Then
        textLines = Split(wordText, vbCrLf) ' Windows形式の改行
    ElseIf InStr(wordText, vbLf) > 0 Then
        textLines = Split(wordText, vbLf) ' Unix形式の改行
    ElseIf InStr(wordText, vbCr) > 0 Then
        textLines = Split(wordText, vbCr) ' Mac形式の改行
    Else
        MsgBox "改行コードが見つかりません。", vbExclamation
        Exit Sub
    End If

    ' Excelのシートに行ごとに転記
    For i = LBound(textLines) To UBound(textLines)
        ThisWorkbook.Sheets(1).Cells(i + 1, 1).Value = textLines(i)
    Next i

    ' Word文書を閉じる
    wordDoc.Close SaveChanges:=False
    wordApp.Quit

    ' オブジェクトを解放
    Set wordDoc = Nothing
    Set wordApp = Nothing

    MsgBox "PDFからExcelへの転記が完了しました（行ごとに分割）。", vbInformation
End Sub
```
NextAuth v5でDiscordのOAuth2.0のスコープを指定するのに手こずった

NextAuth5で翻弄したので備忘録。
Discord({authorization:{ params: ... }}) とかネットには書いてあったけど、できない。

## 権限を追加するには？

結論として、普通に時前で作ればいいだけでした。

```ts
const makeDiscordAuthURL=(...args:string[])=>`https://discord.com/api/oauth2/authorize?scope=${args.join('+')}`

// Usage:
export const auther = NextAuth({
  providers: [
    Discord({
      clientId: /* ... */,
      clientSecret: /* ... */,
      authorization: makeDiscordAuthURL('identify', 'email', 'guilds')
    })
  ],
  // ...
})
```

## 取得するなら

ちなみに、アダプタを使ってないなら基本的にトークンはcallbacks内でしか操れないらしい。

もしトークンを使ってユーザーの情報を取得するなら↓

https://discord.com/developers/docs/resources/application
Cloud Runサービスの修正・再デプロイ手順

実務でGoogleCloudにデプロイ済みのサービスを修正して再デプロイする手段が全くわからん！だったので、調べてまとめました。

1\.　コードの取得
```
# イメージをプル
docker pull gcr.io/your-project-id/your-backend-service:latest

# 一時コンテナを作成してコードをコピー
docker create --name temp-backend gcr.io/your-project-id/your-backend-service:latest
mkdir -p ./backend-src
docker cp temp-backend:/app ./backend-src/app
docker rm temp-backend

# コピーしたファイルの確認
ls -la ./backend-src/app
```
2\.　Dockerfileの作成
```
# backend-srcディレクトリに移動
cd ./backend-src

# Dockerfileを作成
cat > Dockerfile << 'EOF'
FROM gcr.io/your-project-id/your-backend-service:latest
COPY ./app /app
EOF
```
3\.　コードの修正
```
# エディタでコードを開く
cloudshell edit ./backend-src/app/main.py

# または
cloudshell edit ./backend-src/app
```
4\. イメージのビルドとプッシュ
```
# backend-srcディレクトリにいることを確認
cd ./backend-src

# イメージをビルド
docker build -t gcr.io/your-project-id/your-backend-service:updated .

# ビルドしたイメージをGCRにプッシュ
docker push gcr.io/your-project-id/your-backend-service:updated
```
5\.Cloud Runサービスの更新
```
# サービスを新しいイメージで更新
gcloud run services update your-backend-service \
  --image gcr.io/your-project-id/your-backend-service:updated \
  --region asia-northeast1
  ```
6\.デプロイの確認
```
# サービスの状態を確認
gcloud run services describe your-backend-service --region asia-northeast1

# 最新のリビジョン情報を確認
gcloud run revisions list --service=your-backend-service --region asia-northeast1 --limit=1

# ログを確認
gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=your-backend-service" --limit=10
```

今回は少ない修正だったのでひとまずCloud Shell上で行いましたが、デバックのしやすさを踏まえると、ローカルに落としてやる方法の方が良さそうな気がしますね。。

また模索していきたいと思います！
Rのapply関数ファミリー


Rには、データフレームや行列に対して関数を適用するための便利な関数群がある。これらは「applyファミリー」と呼ばれ、それぞれ異なる用途に特化している。以下に主要な関数とその略語の意味、使い方を説明する。

## apply関数
`apply`関数は、行列やデータフレームに対して行または列ごとに関数を適用する。

apply(X, MARGIN, FUN, ...)

- `X`: 行列またはデータフレーム
- `MARGIN`: 1は行、2は列を指定
- `FUN`: 適用する関数

例:
```
matrix <- matrix(1:9, nrow = 3)

1 2 3
4 5 6
7 8 9

apply(matrix, 1, sum)  # １：行ごとの合計を計算（２なら列ごと）

1+2+3=6
4+5+6=15
7+8+9=24

| 行番号 | 値 |
|-------|----|
| 1     | 6  |
| 2     | 15 |
| 3     | 24 |
```
## lapply関数
`lapply`関数は、リストに対して関数を適用し、結果をリストとして返す。`l`は「list」の略。

lapply(X, FUN, ...)

- `X`: リスト
- `FUN`: 適用する関数

例:
```
list <- list(a = 1:5, b = 6:10

lapply(list, sum)  # 各リスト要素の合計を計算。結果はリストとして返される
# 結果: $a [1] 15, $b [1] 40

1+2+3+4+5=15
6+7+8+9+10=40

| 要素 | 合計 |
|-----|------|
| a   | 15   |
| b   | 40   |
```
## sapply関数
`sapply`関数は、`lapply`と似ているが、結果をベクトルや行列に簡略化して返す。`s`は「simplify」の略。

sapply(X, FUN, ..., simplify = TRUE)

- `X`: リストまたはベクトル
- `FUN`: 適用する関数
- `simplify`: 結果を簡略化するかどうか

例:
```
list <- list(a = 1:5, b = 6:10)

sapply(list, sum)  # 各リスト要素の合計をベクトルで返す
# 結果:
#    a  b 
#    15 40


1+2+3+4+5=15
6+7+8+9+10=40

| 要素 | 合計 |
|-----|------|
| a   | 15   |
| b   | 40   |
```
## tapply関数
`tapply`関数は、ベクトルに対して因子ごとに関数を適用する。`t`は「table」の略。

tapply(X, INDEX, FUN = NULL, ...)

- `X`: ベクトル
- `INDEX`: 因子または因子のリスト
- `FUN`: 適用する関数

例:
```
data <- c(1:10)
groups <- gl(2, 5)
tapply(data, groups, sum)  # グループごとの合計を計算



1+2+3+4+5=15
6+7+8+9+10=40
| グループ | 合計 |
|---------|------|
| 1       | 15   |
| 2       | 40   |
```
## mapply関数
`mapply`関数は、複数の引数に対して関数を適用する。`m`は「multiple」の略。

mapply(FUN, ..., MoreArgs = NULL, SIMPLIFY = TRUE, USE.NAMES = TRUE)

- `FUN`: 適用する関数
- `...`: 関数に渡す引数
- `MoreArgs`: 追加の引数
- `SIMPLIFY`: 結果を簡略化するかどうか
- `USE.NAMES`: 結果に名前を付けるかどうか

例:
```
mapply(rep, 1:4, 4:1)  # 各要素を指定回数繰り返す

| 要素 | 繰り返し |
|-----|----------|
| 1   | 4        |
| 2   | 3        |
| 3   | 2        |
| 4   | 1        |
```
これらの関数を使い分けることで、データ処理を効率化できる。
powershell

```powershell:test.ps1
foreach ($day in $date.Keys) {
    $date[$day] = $date[$day] | Sort-Object { 
        [datetime]::ParseExact($_.time, "yyyy年M月d日", $null) 
    } -Descending
}
```

```
$today = Get-Date

# dd日後の期限
$threshold = $today.AddDays(-dd)

# mm日以内のデータを削除
foreach ($day in $date.Keys) {
    $date[$day] = $date[$day] | Where-Object { 
        [datetime]$_."time" -gt $threshold 
    }
}

```

```
$date1 = Get-Date "2023/1/19"
$date2 = Get-Date "2023/1/20"
$date2 -gt $date1
```
```
True
```
```
$text = "hello world"

if ($text -notmatch "goodbye") {
    Write-Output "パターンが見つからなかったので処理を実行"
}
```
```
# 連想配列を定義（曜日ごとの配列）
$a = @{
    'monday' = @('task1', 'task2', 'task3')
    'tuesday' = @('task4', 'task5')
    'wednesday' = @('task6', 'task7', 'task8', 'task9')
}

# 連想配列のキーをループ
foreach ($day in $a.Keys) {
    Write-Host "$day:"
    
    # 各曜日の配列の中身をループ
    foreach ($task in $a[$day]) {
        Write-Host "  $task"
    }
}
```

正規表現
```
"1/1" -match "(?<!\d)1/1(?!\d)"
```
(?<!...) は「この部分の前に 指定したパターンがない 場合にのみマッチする」
\d は「数字（0-9）」を表す
つまり、(?<!\d) は「直前が数字でない」場合にのみマッチする
【Power Apps】アプリで5段階評価を入力してみよう！

## はじめに:point_up:
Power Appsでユーザーからのフィードバックを収集する際、
5段階評価（スター評価やグッド評価、評価によってアイコンが違う等）を使いたい場面は多いですよね。
しかし、標準のコントロールには5段階評価用のコンポーネントがなく、
「どうやって実装すればいいの？」と悩む方も多いのではないでしょうか。

本記事では、ギャラリーやボタンを活用して、シンプルかつ直感的に5段階評価を入力できる方法を解説します！！

`モダンコントロールにレーティングコントロールがありますが、
単体での選択や選択無しが不可のため別方法を使用します。`


どのような画面イメージかというと以下のようなイメージになります:smiley:
一目でわかりやすい5段階入力になっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3719434/c822080c-2a32-443e-88ae-a0387161f00c.png)


## 実装方法:wrench:
では実装方法について解説していきます。
① Power Apps にログインしてアプリを開きます。

② `空の垂直ギャラリー` を置きます。
　 `Items` には以下のようなテーブルを作成します。

```ruby:Items
Table(
    {
        value: 1,
        txt: "かなり悪い",
        icon: Icon.EmojiSad
    },
    {
        value: 2,
        txt: "悪い",
        icon: Icon.EmojiFrown
    },
    {
        value: 3,
        txt: "普通",
        icon: Icon.EmojiNeutral
    },
    {
        value: 4,
        txt: "良い",
        icon: Icon.EmojiSmile
    },
    {
        value: 5,
        txt: "かなり良い",
        icon: Icon.EmojiHappy
    }
)
```
`icon` に設定している値は `クラシックアイコン` の中にあるこちらの5種類を選択しています！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3719434/17ad1ac3-558a-475f-83b5-f7dc0053b63e.png)

③ ギャラリー内にアイコンを設置して、`Icon` を以下のように設定します。

```ruby:Icon
ThisItem.icon
```
ギャラリーのテーブルで `icon` に設定したアイコンがそれぞれ表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3719434/3175ac4d-edea-40e6-bfb6-9c8014208420.png)

④ 次にテキストラベルを設置して、`Text` を以下のように設定します。

```ruby:Icon
ThisItem.txt
```

ギャラリーのテーブルで `txt` に設定した文字がそれぞれ表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3719434/1da307c6-6507-464d-9026-2b962c85ec9a.png)

⑤ アイコンのデフォルトカラーをグレーに設定してアイコンが押された時にだけ色を変える設定を行います。
まずは、③で設定したアイコンの `OnSelect` に以下を設定します。

```ruby:OnSelect
UpdateContext({loc_selecticon:ThisItem})
```

⑥ 続いて `Color` を以下のように設定します。

```ruby:OnSelect
If(
    ThisItem.icon = loc_selecticon.icon,
    RGBA(45,128,40,1),
    RGBA(214,221,224,1)
)
```

⑦ `HoverColor` を以下のように設定します。
　 `HoverColor` とはビジュアルにカーソルを合わせたときの枠線の色を指します。

```ruby:HoverColor
RGBA(45, 128, 40, 1)
```

以上の設定を行うことで冒頭にお伝えした仕様が実装されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3719434/ac7c3276-6be9-4a26-96dd-d3e648e8d97c.png)

アイコンをクリックすると `loc_selecticon` にアイコンのテーブルデータが格納されるので
あとは必要に応じて更新や作成の際に `loc_selecticon` のデータを利用してください。

今回は例としてテーブルのデータを作成していますが、必要に応じたテーブルのデータ数や
内容に変更していただいて構いません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3719434/0d97975a-7ef6-4965-99c4-2f9b0a50f501.png)


## さいごに:dizzy:
今回紹介した方法を使えば、Power Apps で簡単に 5 段階評価の入力を実装できます！
自社のアプリに合ったカスタマイズでぜひ活用してみてください！！
GASでフォームを作成するとき、メールの収集を「確認済み」(Verified)にしたい

# はじめに
こんにちは、ユーゴです。今回は、GASについて紹介します。

# 課題
GASでフォームを作成するとき、「デフォルトでメールアドレスを収集する」の設定を「確認済み」(Verified)にしたい。

![gas_form_verified_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2607452/12506147-7e20-4283-8dda-64d1fdf7bf92.png)


しかし、以下のコードでは「回答者からの入力」になってしまう。

```js
let form = FormApp.create(title);
form.setCollectEmail(true);
```

# 解決
setCollectEmailメソッドと併用して、setEmailCollectionTypeメソッドを使用する。

```js
let form = FormApp.create(title);
form.setCollectEmail(true).setEmailCollectionType(FormApp.EmailCollectionType.VERIFIED);
```

公式リファレンスがないの何故...
GASエディタ上の補完から発見しました。

![gas_form_verified_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2607452/f526757d-454f-459b-b7a5-fa0ba80b7b93.png)


# まとめ
いかがだったでしょうか。今回は、GASからメールアドレスの収集を確認済みにする方法を紹介しました。解決には、公式リファレンスにはないものの、内部的に存在しているメソッドを活用しました。
このように、GASの紹介から、Unity, AWS, 量子コンピューティングなど、幅広い知見の紹介をしております。お役に立てましたら、いいね, フォローなどよろしくお願いします！！
エラーハンドリングのやり方の概要

経緯
プログラムの例を見ていると~~エラー時に~~assertを使っていることが多いが，そんな簡単にプログラムを停止して良いのか？と思ったため下記の質問をした．

質問：「assertは本番用のコードには書かないですか？
組み込みシステムがエラーでプログラム終了したら止まってしまう気がします．
止まったら他のプログラムやOSが監視していて復旧したりするのでしょうか？」(←アホそうな文章ですみません．)
以下はChatGPTの回答．

---

`assert` は基本的に **デバッグ用** のツールとして使われ、**本番環境**（プロダクションコード）には通常書かないことが推奨されます。その理由と、組み込みシステムやエラー処理に関する懸念について詳しく説明します。

### 1. `assert` は本番環境では使わない理由
`assert` は主に開発中にバグを発見するために使用されます。デバッグ時に、プログラムの状態が予期しないものになった場合にその場で中断させ、エラーの原因を特定するための手助けとなります。しかし、**本番環境**（製品版、リリース版）では以下の理由で `assert` を使用しないことが一般的です：

- **プログラムの停止**: `assert` が失敗すると、プログラムが即座に中断されます。特に組み込みシステムや商用ソフトウェアのようなシステムでは、エラーが発生した場合にプログラム全体を停止させることは望ましくないことが多いです。システムが途中で停止してしまうと、サービスが停止したり、機能が動作しなくなったりする可能性があります。
  
- **デバッグ情報**: `assert` は失敗した場合にエラーメッセージを標準エラー出力に表示します。これが本番環境に残ると、セキュリティリスクになる可能性や、ユーザーに不必要な情報を露出させることになります。ユーザーにエラーメッセージが表示されるのも好ましくないことが多いです。

- **パフォーマンス**: `assert` は条件を毎回チェックします。リリース版でデバッグのためのオーバーヘッドを避けるために、`NDEBUG` を定義して `assert` を無効化することが一般的です。しかし、実際に本番コードで何らかのエラーチェックを行う場合、`assert` ではなく、適切なエラーハンドリングを行うことが求められます。

### 2. 組み込みシステムでエラーが発生した場合
組み込みシステムやリアルタイムシステムでは、プログラムがエラーで停止することは非常に重要な問題です。システムの信頼性が求められるため、エラーが発生してもプログラムが完全に停止するのではなく、何らかの回復措置が必要です。以下の方法でエラーを処理することが一般的です：

#### エラーハンドリングと回復
- **エラーログ**: `assert` での中断の代わりに、エラーメッセージやログをファイルや外部デバイスに記録することが一般的です。これにより、エラーの内容を追跡しやすくし、必要に応じて後でデバッグが可能です。

- **エラーコード**: エラーが発生した場合、返り値やエラーコードを使用して、問題を上位のシステムに伝え、必要な回復処理を行うことが推奨されます。

- **例外処理**: 組み込みシステムでは例外処理（try-catchなど）を利用する場合もあります。これはプログラムがエラーを検知した後に、その場でプログラムを停止させることなく、代替の動作を行うための仕組みです。

#### 組み込みシステムでの監視と回復
組み込みシステムやリアルタイムOS（RTOS）を使っている場合、システムが停止するのを避けるための監視機能が組み込まれていることが多いです。例えば：

- **ウォッチドッグタイマー**: 組み込みシステムでは、ウォッチドッグタイマーを使ってシステムを監視し、問題が発生した場合にシステムをリセットして回復を試みることができます。ウォッチドッグタイマーが一定時間内にリセットされないと、システムは自動的にリセットされ、正常な状態に戻るようになります。

- **フェイルセーフ機能**: 組み込みシステムにおいて、重要な処理が失敗した場合に、安全に停止するか、リカバリ処理を行うフェイルセーフ機能を実装することが一般的です。これにより、システムが不安定になることを防ぎます。

- **冗長性**: 重要なシステムやデバイスでは、冗長性を持たせることがあります。例えば、複数のプロセッサや複数の処理経路を用意し、片方で問題が発生しても他の経路で正常に動作できるようにします。

### 3. 本番環境向けのエラーチェック
本番環境でのエラーチェックは、`assert` ではなく、適切なエラーハンドリングを行うべきです。以下は、実際に本番環境で用いる方法です：

- **エラーチェック関数の使用**: エラーが発生した場合に適切にエラーコードを返したり、エラーハンドラを呼び出すような設計が必要です。
  
  例:
  ```c
  int divide(int a, int b) {
      if (b == 0) {
          // エラー処理
          return -1;  // エラーコードを返す
      }
      return a / b;
  }
  ```

- **リカバリ機構**: エラーが発生した際に、システムが自動的に回復するための処理を追加します。たとえば、リセットやフェイルオーバーの機構を設けます。

### 結論
- **`assert` は本番環境には適していません**。デバッグ用のツールであり、本番コードでは使用しないことが一般的です。
- 組み込みシステムや重要なシステムでは、エラー発生時にシステムが停止しないようにするための監視やリカバリ機能（例: ウォッチドッグタイマー、冗長性、フェイルセーフなど）を組み込むことが重要です。
- 本番環境では、エラーハンドリングを適切に行い、エラー発生時にはシステムを安全に回復させる仕組みを整えるべきです。
Microsoft Graph API で使用する SharePoint OnlineのサイトID,リストIDの取得方法

Microsoft Graph APIを使用してSharePoint Online関連の開発をする場合、サイトのサイトIDや各リストのリストIDが必要となります。
Microsoft Graph APIを使用してサイトID,リストIDを取得する方法は[以前の記事](https://qiita.com/miesque/items/e66d991291f47ece032f)に記載しましたが、環境によりGraph API Explorerの実行を許可していない場合がありましたので、Graph API を使用せず、SharePoint REST APIを使用して取得する方法を記載します。

# サイトIDの取得
サイトIDは、次の値の複合であるユニークなIDによって識別され、
[サイトコレクションのホスト名],[サイトコレクションのGUID],[サイトのGUID] という形式です。
・サイトコレクションのホスト名
・サイトコレクションのGUID
・サイトのGUID

### 1. ホスト名
URLから取得
```
[テナント名].sharepoint.com
```
### 2. サイトコレクションのGUID
SharePoint REST API で取得
```
GET https://[テナント名].sharepoint.com/sites/[サイト名]/_api/site/id
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94732/2a27003f-9c42-4a49-ac0c-3c1ef92bbbcc.png)

### 3. サイトのGUID
SharePoint REST API で取得
```
GET https://[テナント名].sharepoint.com/sites/[サイト名]/_api/web/id
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94732/b24e3faa-f8e6-417c-9bcc-5c648b828374.png)

### サイトID
取得したホスト名、サイトコレクションのGUID、サイトのGUIDをカンマで接続した文字列
``` 
サイトID例：
[ドメイン名
].sharepoint.com,88888888-8888-8888-8888-888888888888,99999999-9999-9999-9999-999999999999
```




# リストIDの取得
SharePoint REST APIで取得
```
GET https://[テナント名].sharepoint.com/sites/[サイト名]/_api/web/lists/getbytitle('[リスト名]')/id
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94732/3a4aae8b-0263-4530-ba1f-4205bc4e9b69.png)


※ユーザー情報リストを取得する場合は、リスト名を 'ユーザー情報リスト' または 'User Information List' にします

# 参考
https://learn.microsoft.com/ja-jp/graph/api/resources/site?view=graph-rest-1.0#id-property

https://learn.microsoft.com/ja-jp/graph/api/resources/sharepoint?view=graph-rest-1.0

***
以上です。
記載ミスなどありましたらご指摘いただけると幸いです。
第10回:オフショア開発における学習と継続的改善

## **1. はじめに**

オフショア開発は一度構築すれば終わりではなく、継続的に改善しなければなりません。プロジェクトの振り返りやパフォーマンス測定を通じて、より効率的で質の高い開発プロセスを確立することが求められます。本記事では、オフショア開発の学習と継続的改善の方法について解説します。

---

## **2. パフォーマンスを測定する**

### **(1) KPI を活用する**

**代表的なKPI**
- **開発速度:** スプリントごとの完了タスク数（Velocity）
- **品質指標:** バグ発生率、テストカバレッジ
- **コミュニケーション:** レスポンスタイム、ミーティングの生産性

**例:**
- 「チームのバグ修正時間が平均5日かかっていたが、改善施策を導入した結果、3日に短縮できた。」

**可視化ツールを活用する**
- Jira や Trello のレポート機能を活用し、プロジェクト進捗を追跡。
- Google Data Studio や Power BI でデータを分析し、改善ポイントを特定。

---

## **3. 振り返りを実施する**

### **(1) スプリントレビューとレトロスペクティブ**

**スプリントレビュー:**
- 進捗状況の確認とステークホルダーからのフィードバックを収集。
- 例: 「顧客が 'UIの動作が遅い' というフィードバックをくれたので、パフォーマンス最適化を次のスプリントの優先事項に設定。」

**レトロスペクティブ:**
- 「良かったこと」「改善できること」を振り返り、次のアクションを決定。
- 例: 「オフショアメンバーとの仕様の認識違いが発生 → ドキュメントの詳細化とレビュー時間を増やす。」

**効果的な振り返りのポイント**
- 「問題探し」ではなく「解決策を考える」ことに焦点を当てる。
- オフショアチームにも発言の機会をしっかり与える。

---

## **4. 最新技術とトレンドを取り入れる**

### **(1) 技術トレンドを活用する**

**AIや自動化ツールの活用**
- 「コードレビューを効率化するために、GitHub Copilot を導入。」
- 「テストの自動化に Playwright を導入し、QA の工数を削減。」

**最新のリモートワークツールを活用**
- 例: 「Miro や FigJam を使って、オフショアチームとのブレインストーミングをよりスムーズに。」

### **(2) 継続学習の文化を作る**

**ナレッジ共有の仕組みを作る**
- Confluence で技術ドキュメントを蓄積し、新メンバーのオンボーディングを効率化。
- 例: 「オフショアチームのエンジニアが技術ブログを書くことで、社内ナレッジの強化につながった。」

**勉強会やワークショップを実施する**
- 月に1回、技術勉強会を開催し、オフショアチームと最新技術について学ぶ。

---

## **5. 成功事例**

### **成功事例: 日本企業A社とベトナムオフショアチーム**

**課題:**
- 要件の変更が頻繁に発生し、オフショアチームの対応が遅れる。

**対策:**
- スプリントごとにレトロスペクティブを実施し、変更管理のルールを改善。
- 技術ブログを活用し、ナレッジ共有を強化。

**結果:**
- 変更対応のスピードが向上し、開発効率が20%向上。
- 新規メンバーの立ち上がり時間を短縮。

---

## **6. まとめ**

- **KPI を活用し、パフォーマンスを測定する**
- **振り返りを実施し、継続的に改善する**
- **最新技術とツールを積極的に取り入れる**
- **ナレッジ共有を促進し、チーム全体のスキル向上を図る**

オフショア開発の成功は、一度の施策だけでなく、継続的な改善と学習によって実現されます。今回紹介した方法を取り入れ、長期的な成長を目指しましょう！

このシリーズを通して、オフショア開発の課題や解決策について詳しく解説しました。今後も、より具体的なトピックや実践的な事例を紹介するシリーズを予定していますので、ぜひフォローして次回の記事もご覧ください！
PowerShellでCSVファイルの指定列以降を削除する方法

以前BizRobo!のコミュニティで回答したものなのですが、なかなか良い題材だと思いましたので紹介します。

# 相談内容
ご相談内容の概要は以下の通りです。

---
**CSVファイルの不要な列を削除する方法を知りたい。**

**例:** 1〜26列目は残し、それ以降の列を削除したい。

BizRobo!上でCSVファイルからExcelファイルの変換を行うと、
かなり時間がかかりそうだったため断念した。

**対象のCSVファイル情報:**
- 列数：50
- 行数：14,989
---

## 相談内容についての補足
前提として、BizRobo!の標準機能では、CSVファイルをExcelファイルに直接変換することはできません。

CSVを1行ずつ読み取って変数に格納し、Excelに入力する方法もありますが、このデータ量では非効率です。
どうしても実施する場合は、VBAや他のツールを活用する方が良いでしょう。

また、BizRobo!の機能を使って正規表現やJavaScriptで処理する方法も考えられます。
しかし、BizRobo!ではCSVファイルの内容をアプリケーション内のメモリに読み込んで処理するため、大きなファイルを扱う場合は避けた方が無難です。

# 指定列以降の削除方法
**結論:** PowerShellを使用し、正規表現を使って必要な列を抽出するのが効率的です。

以下のPowerShellコマンドを実行すると、26列目までを残した新しいCSVファイルを作成できます。
~~~powershell
(Get-Content [inputファイル名]) -replace '^((?:[^,]*,){25}[^,]*).*$', '$1' | Set-Content [outputファイル名]
~~~
それぞれやっていることを分解すると以下の通りです。
### コマンドの解説
| コード | 説明 |
|---|---|
| `(?:[^,]*,)` | カンマで区切られたデータを取得 |
| `{25}` | 上記を25回繰り返す（1〜25列目） |
| `[^,]*` | 26列目のデータ |
| `.*$` | 27列目以降のすべて |
| `'$1'` | 26列目までを保持し、それ以降を削除 |
| `Get-Content` | CSVファイルをテキストとして取得 |
| `Set-Content` | 変更後のデータを保存 |

このPowershellコマンドを「ps1」拡張子の実行ファイルにしておき、コマンドラインから実行できるようにすればロボットからでも利用しやすいでしょう。

**コマンド実行時の注意点:**
実行ポリシーによってPowerShellスクリプトが実行できない場合は、以下のようにオプションを指定して実行してください。
~~~powershell
powershell -ExecutionPolicy RemoteSigned -File [ps1ファイル]
~~~

# まとめ
- **BizRobo!単体ではCSVファイルの不要な列を削除するのが難しい**
- **PowerShellを使えば、簡単かつ効率的に特定の列以降を削除できる**
- **ロボットでの実行も可能なため、BizRobo!と組み合わせて活用できる**

参考リンク：[Design StudioでPowerShellを使用する方法](https://knowledge.bizrobo.com/hc/ja/articles/360028894071)

ちなみに、このまとめはAIで生成させてみました。
全体の文章推敲については、ほとんど私が書いたままでしたが、コマンドの解説等Markdown記法の部分は良い感じにして見やすくしてくれました。
第9回:オフショア開発の拡大とコスト最適化

## **1. はじめに**

オフショア開発は、コスト削減やリソースの最適活用を目的として導入されることが多いですが、適切にスケールしなければ、逆に管理負担の増加や品質低下につながる可能性があります。本記事では、オフショアチームを拡大する際のポイントと、コスト最適化の戦略について解説します。

---

## **2. オフショア開発のスケール戦略**

### **(1) いつ拡大すべきか？**

オフショアチームの拡大は、以下のようなタイミングで検討すると効果的です。

**開発スピードを上げる必要がある**
- 新機能開発のペースを加速したい。
- 既存の開発チームでは対応しきれない。

**ビジネス成長に伴うリソース不足**
- 事業拡大によりエンジニアが不足している。
- コアメンバーがより戦略的な業務に集中できるようにしたい。

**コスト削減が求められる**
- 予算削減のため、より低コストなリソースを活用したい。
- 日本国内の採用が難しいため、海外リソースを活用したい。

**例:**
- 「日本本社の開発チームが新規プロジェクトで手一杯になり、追加機能の開発が遅れていたため、オフショアチームを増員し、開発スピードを向上させた。」

---

### **(2) 拡大時の課題と対策**

**課題 1: チームメンバーの質を維持するのが難しい**
**対策:**
- 技術スキルだけでなく、コミュニケーション能力を重視して採用する。
- 小規模チームで試験運用し、パフォーマンスを評価してから拡大する。

**課題 2: 管理負担の増加**
**対策:**
- プロジェクトマネージャーをオフショア側にも配置する。
- 自動化ツール（Jira, Confluence）を活用して管理の手間を削減する。

**課題 3: 文化の違いによる非効率**
**対策:**
- 文化トレーニングを実施し、相互理解を深める。
- コアメンバーを一定期間オフショア拠点に派遣し、チームの結束を高める。

**例:**
- 「急激にオフショア開発を拡大した結果、品質が低下。採用基準を見直し、トライアル期間を設けることで、長期的に優秀なメンバーを確保できるようになった。」

---

## **3. コスト最適化の戦略**

### **(1) 人件費とパフォーマンスのバランスを取る**

**高スキルなエンジニアには、重要な開発を担当させる**
- 例: 「コアアーキテクチャの設計は経験豊富なエンジニアが担当し、一般的な開発タスクはジュニアエンジニアに割り当てる。」

**地域ごとの給与水準を活用**
- 例: 「東南アジアでは高スキルのエンジニアを日本の半分以下のコストで採用できる。」

**パートナーシップを活用し、安定したリソース確保を行う**
- 長期契約を結び、コストを抑える。

---

### **(2) ハイブリッドモデルの活用**

**オンショア + オフショアの組み合わせ**
- 「要件定義や設計は日本のエンジニアが担当し、開発・テストはオフショアチームが実施する。」

**リモートワークとオフショアを組み合わせる**
- 「リモートワークを活用し、日本側のメンバーもオフショアチームと同じ働き方をすることで、一体感を強化。」

---

### **(3) 自動化と標準化によるコスト削減**

**CI/CDの導入で開発効率を向上**
- 「GitHub ActionsやJenkinsを活用し、テストとデプロイを自動化することで、開発時間を20%短縮。」

**ドキュメントの標準化でコミュニケーションコストを削減**
- 「Confluenceを利用してナレッジを蓄積し、新メンバーのオンボーディングを迅速化。」

---

## **4. 成功事例**

### **成功事例: 日本企業X社とインドのオフショア開発チーム**

**課題:**
- 開発コストが高騰し、利益率が低下。

**対策:**
- 高スキルなエンジニアには重要タスクを任せ、それ以外はコストの低いリソースを活用。
- 開発フローを自動化し、工数を削減。

**結果:**
- コストを30%削減し、開発スピードも向上。
- 品質を維持しながら、リリース頻度を増やすことに成功。

---

## **5. まとめ**

- **拡大のタイミングを見極める**
- **採用基準と管理体制を整備する**
- **コストとパフォーマンスのバランスを最適化する**
- **ハイブリッドモデルと自動化を活用する**

オフショア開発を拡大する際は、単に人数を増やすのではなく、戦略的に計画を立てることが重要です。適切な管理とコスト最適化を行うことで、持続可能な開発体制を構築できます！
次回は **[第10回:継続的な学習と改善](https://qiita.com/BNR-Satoshi/items/0be59ec7262a565c0b79)** について解説します！
REST API で Tableau Server へサインインも出来なかった私。。

ちょっと Tableau Server に REST API を発行する検証をしたくて、signin を

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4038260/e21c5c47-a653-447d-a933-ebbb211b0942.png)

できない？？ 

401 Unauthorized。何度もパスワードを見直して、間違ってるわけもない。

結局色々試行錯誤した結果、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4038260/32672536-b7ac-4b6f-9cde-cb87e77bd5a6.png)

Default サイトのときにはサイト指定がいりませんでした。。こんな大事なこと書いておいてｙ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4038260/a69dedea-9310-48e9-adce-7d8ab09404e1.png)

書いてありました。。
Default サイトをしていするときだけは、ご注意を！
第8回:リスク管理と契約の最適化

## **1. はじめに**

オフショア開発において、契約とリスク管理はプロジェクト成功の鍵を握ります。契約が曖昧であれば、スコープのズレや納期遅延が発生し、最悪の場合はプロジェクトの中断につながる可能性があります。また、知的財産の保護や法的コンプライアンスを考慮しないと、企業の信用リスクが高まります。

本記事では、オフショア開発におけるリスクを最小限に抑え、安定した契約を構築するためのポイントを解説します。

---

## **2. オフショア開発のリスクとは？**

オフショア開発において発生しやすいリスクは、以下のように分類できます。

### **(1) スコープの変更リスク**
要件が曖昧なまま開発が進むと、途中で仕様変更が発生し、コスト増加や納期遅延の原因となる。

**例:**
- 「開発の途中で機能追加が頻繁に発生し、最終的な納期が2ヶ月遅れた。」

### **(2) 品質リスク**
品質管理が不十分だと、バグが多発し、メンテナンスコストが増加する。

**例:**
- 「テストプロセスが不十分だったため、リリース後に重大なバグが見つかり、修正対応に3週間かかった。」

### **(3) 知的財産（IP）とセキュリティリスク**
コードの漏洩やデータの不正利用を防ぐための対策が必要。

**例:**
- 「外部の開発会社が機密情報を適切に管理せず、競合他社に情報が流出した。」

### **(4) コミュニケーションリスク**
時差や言語の違いにより、意思疎通が難しくなり、誤解が生じる。

**例:**
- 「日本側が伝えた仕様の意図が正しく伝わらず、誤った実装が進んでしまった。」

### **(5) 契約・法的リスク**
契約内容が不明確だと、トラブル発生時に責任の所在が曖昧になる。

**例:**
- 「契約にサポート期間が明記されておらず、追加の修正対応を依頼した際に高額な請求を受けた。」

---

## **3. リスクを軽減するための契約戦略**

### **(1) 契約内容を明確化する**

**SLA（サービスレベル契約）の導入**
- 開発スコープ、品質基準、納期などを明確に定める。
- 例：「APIのレスポンスタイムは100ms以内とする。」

**変更管理プロセスを定義**
- 仕様変更の際に、どのような手続きが必要か事前に合意する。
- 例：「スコープ変更が発生した場合、追加費用と影響範囲をドキュメント化し、双方の承認を得る。」

**支払い条件の明確化**
- マイルストーンベースでの支払いスケジュールを設定。
- 例：「機能A完成後に50%、リリース後に50%を支払う。」

---

### **(2) 品質保証とセキュリティ対策**

**コードレビューとテストプロセスを契約に含める**
- コード品質基準（例: lintルール、テストカバレッジ）を設定。
- 例：「単体テストカバレッジ80%以上を達成すること。」

**データ保護対策を明記**
- 機密データの取り扱いやアクセス権限を制限する。
- 例：「開発チームには本番データではなく、マスキングデータを提供する。」

**NDA（秘密保持契約）の締結**
- 知的財産や機密情報の保護を明記。
- 例：「開発者はプロジェクト終了後もコードを第三者に共有してはならない。」

---

### **(3) コミュニケーションとトラブル対応**

**定期的なステータスミーティングを契約に含める**
- 進捗報告の頻度を契約で定める。
- 例：「週1回の進捗ミーティングを実施し、報告資料を提出する。」

**トラブル発生時の対応フローを明確化**
- バグ修正や遅延が発生した場合の対応策を記載。
- 例：「重大なバグは発生後24時間以内に修正計画を提出する。」

---

## **4. 成功事例**

### **成功事例: 日本企業E社とベトナムのオフショア開発チーム**

**課題:**
- 契約内容が曖昧で、仕様変更のたびに追加費用が発生。

**対策:**
- SLAを導入し、スコープと変更管理ルールを明確化。
- 定期的な進捗レビューを実施し、早期にリスクを特定。

**結果:**
- 仕様変更時のトラブルが減少し、開発コストを15%削減。
- 進捗の透明性が向上し、納期遅延がなくなった。

---

## **5. まとめ**

 **契約内容を明確にし、SLAを活用する**
 **品質保証とセキュリティ対策を強化する**
 **コミュニケーションプロセスを契約に組み込む**
 **リスク発生時の対応策を事前に定める**

オフショア開発において、適切な契約とリスク管理を行うことで、長期的な成功を実現できます！
次回は **[第9回:スケーリングとコスト最適化](https://qiita.com/BNR-Satoshi/items/ee6a579d0caec4a907d3)** について解説します！
LocalStackを使用して、ローカル環境でS3を使ってみた

# はじめに

今回は、LocalStackの環境構築と、エミュレートしたS3へのファイルアップロード＆ブラウザでの表示を行いました。
実務でLocalStackを触る機会があり、興味があったので実際に自分でも環境構築から行ってみました。
LocalStackの環境構築部分でいくつか詰まったので、そこも共有できたらと思います。


# 参考にした記事
こちらの記事を参考にしました。
LocalStackに関する説明などもこちらに記載されているので、興味がある方は見てみてください。

https://qiita.com/Brutus/items/89eb03be7c7bd9d6911d

# LocalStackの環境構築

今回は、ローカル環境(venv上）にlocalstack、awscli、awscli-localをインストールしました。
後述しますが、awscliパッケージがインストールされていないと、awscli-localのコマンドが使用できないことに注意してください。

実施環境：
- Python 3.11.9
- pip 24.0
- Docker（LocalStack CLIではDocker環境が必要）

### localstack

pipを使用してLocalStackをインストールします。

`$ pip install localstack`

この時、パスの長さによるエラーが出た場合は、以下の記事の手順を実行することで解消することができます。

https://qiita.com/acekame/items/b0fde53a0efa59fcaacf


### awscli-local
awslocalコマンドを使用するためにawscli-localをインストールします。

`$ pip3 install awscli-local`

### awscli

awscli-localをインストールしただけだとawslocalコマンドは動かないので、awscliをインストールしていきます。
awscli-localはawscliをラップしたものなので、当たり前ではあるのですが、awslocalコマンドを使用した際に出たエラーが「Segmentation fault」だけだったので、解決方法を見つけるのに少し時間がかかりました。

ということでインストールしていきます。

`$ pip install awscli`

## 動作確認

まずはLocalStackが起動できるか確認します。

以下のコマンドを入力して、起動していきます。
ほかに同じポートを使用しているサービスがあるとうまく起動できないので注意してください。（自分は最初それで起動できませんでした…）
`$ localstack start -d`

```

     __                     _______ __             __
    / /   ____  _________ _/ / ___// /_____ ______/ /__
   / /   / __ \/ ___/ __ `/ /\__ \/ __/ __ `/ ___/ //_/
  / /___/ /_/ / /__/ /_/ / /___/ / /_/ /_/ / /__/ ,<
 /_____/\____/\___/\__,_/_//____/\__/\__,_/\___/_/|_|

- LocalStack CLI: 4.2.0
- Profile: default
- App: https://app.localstack.cloud
...
```

次にawslocalコマンドが動くかを確認します。
以下のコマンドを実行して、バージョン情報が出てくれば問題ありません。

`$ awslocal --version`


## 実際に使ってみる

awslocalコマンドを使用して、LocalStackでエミュレートされたS3に対してファイルのアップロードを行い、アップロードされたファイルをブラウザから確認するということを行いました。

まずは現在開いているディレクトリに、S3へアップロードする用のファイルを作成します。
今回はtxtファイルとhtmlファイルを作成しました。
この時、UTF-8で保存するとブラウザで表示する際に文字化けしてしまうため、Shift JISで保存します。
(後で調べたらアップロードの際にコンテンツタイプにより正しく表示されることがわかりました。
例：`--content-type "text/html; charset=utf-8"`)


```test.txt
# テスト用のファイル

```


``` test.html
<h1>HTMLファイル</h1>
```

次にS3バケットを作成します。
以下のコマンドでバケットを作成できます。
`$ awslocal s3 mb s3://test-bucket`

```
$ awslocal s3 mb s3://test-bucket
make_bucket: test-bucket
```

続いて、S3バケットに先ほど作成したファイルをアップロードしていきます。
以下のコマンドでtest.txtファイルをtest-bucketにアップロードすることができます。
`$ awslocal s3 cp ./test.txt s3://test-bucket`

```
$ awslocal s3 cp ./test.txt s3://test-bucket
upload: .\test.txt to s3://test-bucket/test.txt
```

同様にhtmlファイルもアップロードします。

アップロードが完了したら、今度はブラウザで表示して確認します。

- S3にアップロードされているコンテンツの一覧
    - http://localhost:4566/test-bucket/
- テキストファイルの表示
    - http://localhost:4566/test-bucket/test.txt
- HTMLファイルの表示
    - http://localhost:4566/test-bucket/test.html

HTMLファイルはHTMLが読み込まれてこのように表示されると思います。

![S3アップロードHTML.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689201/f6d6ea09-a203-4555-9aa4-8d7d0e650ab8.png)



# まとめ

今回はLocalStackを使ってエミュレートされたS3へのアップロードを試してみました。
LocalStackでは、S3のほかにも、EC2やLambdaといったサービスが使えるので、今後開発環境やテスト環境で必要になった際には覚えておきたいと思います。
データベース入門

### **1. データベースとは？**  
データベース（DB）とは、**データを整理して保存・管理するシステム** のこと。  
SQLを使用してデータの追加・検索・更新・削除（CRUD）が可能。  

- **RDB（リレーショナルデータベース）** → MySQL, PostgreSQL, Oracle など
- **NoSQL** → MongoDB, Redis など（テーブルを持たず柔軟なデータ構造）

---

### **2. 1対1（One-to-One）のテーブル例**
1対1の関係は、1つのレコードが別のテーブルの1つのレコードにのみ対応する関係。  
**例：ユーザーとユーザーの詳細情報**

#### **📌 テーブル構造**
```sql
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

CREATE TABLE user_profiles (
    user_id INT PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    phone VARCHAR(20),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
```
- `users` に1つのレコードがあると、`user_profiles` にも **1つだけ** 対応するデータがある。

#### **📌 1対1の `JOIN`**
```sql
SELECT users.id, users.name, user_profiles.email, user_profiles.phone
FROM users
INNER JOIN user_profiles ON users.id = user_profiles.user_id;
```

---

### **3. 1対多（One-to-Many）のテーブル例**
1つのレコードが、別のテーブルの **複数のレコードに対応** する関係。  
**例：ユーザーと投稿（1人のユーザーが複数の投稿を持つ）**

#### **📌 テーブル構造**
```sql
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

CREATE TABLE posts (
    id INT PRIMARY KEY,
    user_id INT NOT NULL,
    content TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
```
- `users` の `id` が `posts.user_id` に **複数回登場可能** → **1対多の関係**

#### **📌 1対多の `JOIN`**
```sql
SELECT users.name, posts.content, posts.created_at
FROM users
INNER JOIN posts ON users.id = posts.user_id
ORDER BY posts.created_at DESC;
```
→ **ユーザーごとの投稿を取得し、新しい順に並べる**

---

### **4. 多対多（Many-to-Many）のテーブル例**
1つのレコードが、別のテーブルの **複数のレコードに対応** し、かつ **その逆もある**。  
**例：生徒と授業（1人の生徒が複数の授業を受け、1つの授業に複数の生徒がいる）**

#### **📌 テーブル構造**
```sql
CREATE TABLE students (
    id INT PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

CREATE TABLE classes (
    id INT PRIMARY KEY,
    title VARCHAR(100) NOT NULL
);

CREATE TABLE student_classes (
    student_id INT NOT NULL,
    class_id INT NOT NULL,
    PRIMARY KEY (student_id, class_id),
    FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE,
    FOREIGN KEY (class_id) REFERENCES classes(id) ON DELETE CASCADE
);
```
- **中間テーブル `student_classes` を作ることで、多対多を表現**
- `student_id` × `class_id` の組み合わせがユニーク（`PRIMARY KEY (student_id, class_id)`）

#### **📌 多対多の `JOIN`**
```sql
SELECT students.name, classes.title
FROM students
INNER JOIN student_classes ON students.id = student_classes.student_id
INNER JOIN classes ON student_classes.class_id = classes.id;
```
→ **生徒と受講している授業を取得**

---

### **🔥 まとめ**
| 関係 | 例 | テーブル設計 |
|------|----|------------|
| **1対1** | `users` ↔ `user_profiles` | `user_profiles.user_id` を `PRIMARY KEY` にする |
| **1対多** | `users` ↔ `posts` | `posts.user_id` に `FOREIGN KEY` を設定 |
| **多対多** | `students` ↔ `classes` | 中間テーブル `student_classes` を作成 |
第7回:信頼とコミットメントの構築

## **1. はじめに**

オフショア開発では、開発チームと発注側の間に信頼関係が築かれていないと、プロジェクトの成功は難しくなります。単なる「外注先」としてではなく、「パートナー」として協力し合うことが重要です。信頼とコミットメントを高めることで、チームのモチベーションが向上し、品質の高い成果を生み出せます。

本記事では、オフショアチームと強固な信頼関係を築き、長期的に成功するための戦略について解説します。

---

## **2. 信頼関係を築くための課題**

オフショアチームとの信頼関係を築く際に発生する主な課題は以下の通りです。

1. **透明性の不足**
   - 進捗状況や課題が適切に共有されない。
   - 双方の期待値がズレている。
   
2. **責任の不明確さ**
   - 各メンバーの役割と責任が曖昧。
   - タスクの優先順位が明確でない。
   
3. **モチベーションの低下**
   - オフショアチームがプロジェクトに対して当事者意識を持ちにくい。
   - 成果が適切に評価されず、やる気を失う。

4. **文化的な違い**
   - コミュニケーションのスタイルが異なる。
   - 意見を率直に言いにくい文化がある。

---

## **3. 信頼とコミットメントを築くための戦略**

### **(1) 透明性のある情報共有**

**オープンなコミュニケーションを促進する**
- 定期的なミーティングを実施し、進捗や課題を共有。
- 例：「毎週のスタンドアップミーティングで、現在の進捗、課題、次のアクションを明確にする。」

**タスク管理の可視化**
- Jira, Trello, Asanaなどのツールを活用し、誰が何を担当しているのか明確化。
- 例：「Jiraで各タスクのステータスを共有し、遅延が発生しそうなタスクを事前に察知する。」

**ドキュメントを整備する**
- Notion, Confluenceを活用し、すべての仕様やルールをドキュメント化。
- 例：「開発プロセスやテストケースをConfluenceで管理し、新メンバーがスムーズにキャッチアップできるようにする。」

---

### **(2) 責任と権限の明確化**

**明確な役割分担**
- プロジェクトの責任範囲を明確にし、各メンバーが主体的に動ける環境を作る。
- 例：「PO（プロダクトオーナー）がビジネス要件を定義し、開発チームが技術的な実装をリードする。」

**KPIの設定**
- プロジェクトの成功を測るための指標を設定し、定期的に評価。
- 例：「開発スピード（Velocity）、バグ修正時間（Bug Fix Time）、レビュー品質（Code Review Score）などをKPIとして設定。」

**意思決定プロセスの明確化**
- 誰がどのレベルの決定を下せるか明確にする。
- 例：「技術的な意思決定はTech Leadが行い、ビジネス要件の変更はPMが承認する。」

---

### **(3) モチベーションを向上させる施策**

**成果を適切に評価・報酬を提供**
- 成果を見える化し、貢献を適切に評価する仕組みを導入。
- 例：「月次のMVP（Most Valuable Player）を表彰し、Slackで全員に共有。」

**オフショアチームにも意思決定の機会を与える**
- 技術選定やプロセス改善の提案を受け入れる。
- 例：「新しいCI/CDツールの導入について、オフショアチームの意見を積極的に取り入れる。」

**定期的なフィードバックを実施**
- 1on1ミーティングを行い、個々の課題や希望をヒアリング。
- 例：「月1回の1on1で、仕事の満足度や改善点について意見交換を行う。」

---

### **(4) 文化的な違いを理解し、尊重する**

**異文化理解のためのトレーニングを実施**
- 日本とオフショア国の文化的な違いを理解する研修を実施。
- 例：「日本企業の意思決定プロセスや、オフショアチームの価値観を相互に理解するワークショップを開催。」

**オフライン・オンラインでのチームビルディングを強化**
- バーチャルランチや懇親会を通じて、業務外でのコミュニケーションを増やす。
- 例：「3ヶ月に1回、リモートでゲーム大会を開催し、チームの結束を強める。」

**相手の文化を尊重したコミュニケーション**
- 直接的な表現を避ける文化のチームには、意見を引き出す工夫をする。
- 例：「インドの開発チームには、最初に雑談を交えながら話を進めることで、よりオープンな意見交換を促す。」

---

## **5. 成功事例**

### **成功事例: 日本企業D社とフィリピンのオフショア開発チーム**

**課題**:
- オフショアチームが単なる「作業者」になっており、主体性がない。

**対策**:
- 1on1ミーティングを定期的に実施。
- 文化的な違いを学ぶためのワークショップを開催。
- 成果を可視化し、適切に評価。

**結果**:
- チームのエンゲージメントが向上し、プロジェクトの生産性が20%アップ。
- 新しい技術提案が増え、開発の質が向上。
- オフショアチームの定着率が向上。

---

## **6. まとめ**

- **透明性のある情報共有を徹底する**
- **責任と権限を明確にする**
- **モチベーションを高める仕組みを導入する**
- **文化的な違いを理解し、尊重する**

信頼とコミットメントを強化することで、オフショア開発がよりスムーズに進み、長期的な成功につながります！
次回は **[第8回:リスク管理と契約](https://qiita.com/BNR-Satoshi/items/ebb9ce3bf224e939f32f)** について解説します！
【初心者の備忘録】モバイル版でもリンクを押下したら別タブは1個だけ開いて、以降はそっちにフォーカスしたい。

## インデックス

1. はじめに
2. 一番シンプルな解決方法
3. かなり回りくどい解決方法(変わった人向け)
4. 感想

### はじめに

　モバイル版とPC版のどちらでも使うことのできる、「リンクを押下したときに別タブで1個だけ開いて、もう一つ開こうとしたら開いた方のタブにフォーカスする」メソッドについて、2通りの方法を紹介する。達成したい要件としては以下の通りである。

 - リンクが貼られているウェブサイトAがあると仮定する
 - 特にタブがない時にそのリンクを押下すると、別のタブBが開かれる
 - タブBが開かれている時にウェブサイトAからリンクを押下すると、タブBにフォーカスする

 　**インデックス2**の方法を用いるのが一番簡単かつ最適解なのだが、**インデックス3**のパターンもどこかで使われるかもしれないので、一応書いておく。

  　また、次のような構造をしていると仮定する。

   ```
   ......(中略)
       ┣ hogepage/ 
           ┣ App.tsx
   ......(中略)
       ┣ pages/ 
           ┣ link/ 
               ┣ hogepageLink.tsx
   ......(中略)
   ```

### 一番シンプルな解決方法
　通常は、以下のように設定すれば問題はない。

 ```App.tsx
 ......(中略)

 type AppProps = {
 onClick: () => void;
 };
 
 const App = React.FC<AppProps> = ({ onClick, ...props }) => {
 ......(中略)
 return (
 <div { ...props }>
 ......(中略)
 <HogepageLink target="hogehoge" onClick={onClick}>HogeHoge<HogepageLink />
 ......(中略)
 </div>
 )
 };
 ```

 ```HogepageLink.tsx
 ......(中略)
 type HogepageLinkProps = StylableProps &
  Omit<LinkProps, 'to'> & {
    children: React.ReactNode;
  };

const HogepageLink: React.FC<HogepageLinkProps> = ({
  className,
  children,
  ...props
}) => (
  <Link to="/hogepage" className={className} {...props}>
    {children}
  </Link>
);

export default styled(HogepageLink)``;
 ```
 　結論から言えば、この方法を取れば間違いない。`/hogepage` のハードコードはよろしくないが、あくまで構造を簡潔に書いておきたい感じなので、その点は一旦目を瞑っていただきたい。

### かなり回りくどい解決方法(変わった人向け)
　「一番シンプルな解決方法」からかなり遠ざかっているが、一応これでも動作する。ただし、めちゃくちゃ見づらい。メリットがあるとすれば、`target`をつけ忘れても機能するという事ではあるが、`onClick`を指定しようものならこれは機能しなくなるので要注意である。

 ```App.tsx
 ......(中略)
 const App = React.FC = ({ ...props }) => {
 ......(中略)
 return (
 <div { ...props }>
 ......(中略)
 <HogepageLink>HogeHoge<HogepageLink />
 ......(中略)
 </div>
 )
 };
 ```

 ```hogepageLink.tsx
  ......(中略)
type HogepageLinkProps = StylableProps &
  Omit<LinkProps, 'to'> & {
    children: React.ReactNode;
  };

const HogepageLink: React.FC<HogepageLinkProps> = ({
  className,
  children,
  ...props
}) => {
  // 無限に新しいタブを開きたくないので、1つ対象のタブが開かれていたらそれにフォーカス
  // 独自にクリックイベントを作っているのでonClickに値を挿入してはいけない
  const hogepageURL = "/hogepage";
  const tabName = 'hogepageTab';

  const handleClick = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
    e.preventDefault();

    const openedTab = window.open('', tabName);
    if (openedTab && !openedTab.closed) {
      openedTab.location.href = hogepageURL;
      openedTab.focus();
    } else {
      const newTab = window.open(hogepageURL, tabName);
      if (newTab) {
        newTab.name = tabName;
      }
    }
  };

  return (
    <a
      href={hogepageURL}
      className={className}
      onClick={handleClick}
      {...props}
    >
      {children}
    </a>
  );
};

export default styled(HogepageLink)``;
 ```

 ### 感想
　最初は「かなり回りくどい解決方法」の方を採用してやり遂げた気分になっていたのに、よくコードを見渡してみると、「一番シンプルな解決方法」だけで解決してしまったので、なんとも言えない気分になったのを覚えている。仕様の確認は、非常に大事である。