every Tech Blog

株式会社エブリーのTech Blogです。

開発組織のナレッジ共有とコミュニケーションを促進する社内イベント「TechTalk」の紹介

はじめに

こんにちは。DELISH KITCHEN 開発部 SERS グループ兼、CTO 室 DevEnable グループ所属の池です。

SERS グループでは主に小売向けプロダクトの開発を行なっており、DevEnable グループでは社内開発組織活性化に向けた活動を行なっています。

今回は DevEnable グループの活動の一つである、”TechTalk” という社内技術共有会の取り組みにスポットを当てたイベントレポートをお届けします。

DevEnable グループとは

2024 年 2 月に DevEnable グループが新設されました。有志が組織活性化委員会として行なっていた活動を正式な組織活動としてより広く深く取り組むためのグループです。

私たち DevEnable グループのミッションは「社内外から憧れる開発組織へ」です。そのミッションの実現に向けて採用・発信・成長環境などの課題を改善するため、施策の検討から実施まで推進しています。

様々な施策を推進している中で私は現在主にオンボーディングプロセスの改善や TechTalk の運営などの施策推進を行っています。

TechTalk とは

TechTalk とはエブリーが月次で開催している社内技術共有の場です。

エブリーでは DELISH KITCHEN、トモニテ、TIMELINE と各事業部に分かれており、普段はそれぞれが別チームとして動いているため、チーム横断でのコミュニケーションが取りづらいという組織体系による課題があります。

チームを超えたナレッジ共有や情報共有ができずに、チームごとに同じような技術検証や課題に取り組んでしまうと、無駄な労力に繋がってしまいます。

活性化組織委員会のリーダーを担っていた國吉さんが書いた 挑戦 Week の記事 にも上記課題への言及があるのでご参照ください。

TechTalk はこの課題を解決するための取り組みの一つであり、以下の目的を持っています。

  • 組織横断したナレッジ共有
  • エンジニアの技術的知見の共有
  • 開発部全体でのエンジニアの交流

TechTalk 実施内容

TechTalk のアジェンダは次のとおりです。

  • 新しく入社されたメンバーの自己紹介
  • 開発部 ALL HANDS
  • ポストモーテム共有会
  • ライトニングトーク(LT)
  • 懇親会

オフライン参加者はフリースペースに集まり、オンライン参加者は Zoom で繋ぎます。

自己紹介

新たに加わったメンバーの自己紹介を行います。 開発部全員が集まる場で自己紹介することによって、組織へのスムーズなオンボーディングを促します。 このように開発部全員が集まる機会は少ないので、貴重な機会となっています。

開発部 ALL HANDS

ALL HANDS では、各グループの OKR やプロジェクト進捗、課題やトピックスについて共有します。

これにより、開発部の各部門全体の動向について把握することができます。

ALL HANDSの様子

ポストモーテム共有会

ポストモーテムとはインシデントについてまとめた文書のことをいいます。 エブリーではインシデントが発生した際には、関係者全員で振り返りを行い、ポストモーテムを作成する文化が根付いています。

同じインシデントを組織内で繰り返さないため、ポストモーテムの内容を共有するセクションを設けています。

LT

続いて LT セクションです。今回の発表は以下の 3 つでした。

  • Vue 3.4 アップデート:開発者が知っておくべきこと
  • push 通知について勉強しました
  • DAP の概要の理解を目指して

最新アップデート内容の共有から、担当を超えた技術領域について学んだ話や、DAP(Delish App Platform)という社内プラットフォームの技術共有など、多岐にわたるトピックが発表されました。

ここからはケータリングのピザを食べながらワイワイと LT 会を行います。

ケータリング

LT1

LT2

LT3

懇親会

LT の後は懇親会に移ります。フリースペースでケータリングを食べながらエンジニア同士が交流を深めます。 普段の業務では会話する機会の無い他部署の方と交流を深めることができます。

懇親会の様子

運営に携わった所感

私は 2024 年 2 月度の TechTalk から運営に携わりました。 運営側の視点に立ってイベントの意義を考えると、単にイベントを運営するということではなく、開発組織の文化形成や、エンジニアの成長を支える重要な役割を担っていると実感しました。 運営側から参加することで、適切な時間配分や時間管理方法はあるか、より質疑応答が活発になるためにはどうすれば良いか、など今までとは異なる視点でイベントのあるべき姿を考えるようになったと思います。 発表者がスムーズに発表できる環境を整えると同時に、参加者にとって意義のあるイベントになるように努めることが重要だと認識しました。 今後も、参加者の声を大事にして、さらに意義のあるイベントにしていけるよう改善を続けていければと考えています。

おわりに

エブリーでは、TechTalk をはじめとする多くの取り組みを通じて、技術者が互いに刺激を受け、成長を続ける環境を大切にしています。 今後も新たな発見と交流の場となることを期待し、TechTalk を開催していく予定です。

また、これからも DevEnable グループとして「社内外から憧れる開発組織」を目指し、働きやすい開発組織作りを追求していきます。 他のイベントを開催した際には同様にイベントレポートをお届けできればと思うのでどうぞご期待ください!

TypeScriptのコードをBranded Primitiveでもう1歩型安全へ

お久しぶりです,トモニテ開発部でSoftware Engineer(SE)をしている鈴木です.
私が普段実装しているトモニテ相談室のフロントエンドはTypeScriptを採用しているのですが,トモニテ相談室の実装中にTypeScriptでは検出することが出来ないミスをしてしまい,原因解明までに時間を要した経験があります.
この経験からTypeScriptを普段より少し型安全にする手法を学んだので,本記事で具体例を交えながら紹介させていただこうと思います.

はじめに

TypeScriptは型を区別するための方式として構造的型付けを採用しています.
したがって,type宣言子による宣言は単に構造に対してエイリアスを張っているに過ぎず,トランスパイラはエイリアスの参照先の構造のみを検査しています.
この自由度は名前的型付けとは対称的であり,TypeScriptがJavaScriptに対してシームレスに型システムを導入することが出来た要因の一つとなっています.
一方で,この自由度ゆえにエンジニアがミスをしてしまった場合にもトランスパイラが見逃してしまう可能性があります.
どのようなミスを見逃してしまうのかを早速皆さんに共有させていただきたいところですが,逸る気持ちを抑え,まずは構造的型付けと名前的型付けの特徴を簡単に整理します.

構造的型付けと名前的型付け

型システムが型を区別するための方式には構造的型付け(Strucural Typing)と名前的型付け(Nominal Typing)の2種類があります.
前者は型の区別の際に型の"構造"に着目し,後者は型の区別の際に型に与えられた"名前"に着目します(両者とも読んで字の如くですね).
したがって,以下のような型TUがあったとき,構造的型付けでは型TUは等しいと見なされ,名前的型付けでは型TUは異なると判定されます.

type T = number;
type U = number;

以下のようなオブジェクト型の場合も同様です.

type User = {
    id: number;
    name: string;
}
type Counselor = {
    id: number;
    name: string;
}

構造的型付けが原因で見逃してしまうミス

以下のような,ユーザーIDを渡すと該当するIDを持つユーザーを返すTypeScriptの関数を考えます.

function getUserById(id: User['id']): User {
    return {
        id: 1,
        name: "鈴木",
    };
}

以下のようにUser['id']型の値を渡した場合にはもちろん想定通りの挙動をします.

const userId: User['id'] = 1;
const ret = getUserById(userId)

ここで,getUserByIdに対してCounselor['id']型の値を渡すことを考えてみます.
引数idUser['id']型であることから,これ以外の型の値を渡した場合にはトランスパイラが検出し,エンジニアにメッセージを出力して欲しいものです.
しかし,期待に反してトランスパイラは以下のようにCounselor['id']型の値を渡した場合も何もメッセージを出力すること無く,問題なくトランスパイルを終えます.

const counselorId: Counselor['id'] = 1;
const ret = getUserById(counselorId)

これはTypeScriptが型を区別するための方式として構造的型付けを採用していることが原因です.
先述の通り,type宣言子はあくまで構造に対してエイリアスを張るだけであるため,User['id']Counselor['id']number型にエイリアスを張っているに過ぎず,トランスパイラは両者を区別しないのです!
これは良し悪しではなく,単に言語仕様なので仕方のない事なのですが,サービス上の各モデルが共通で持つidのようなプロパティは区別出来るとエンジニアのミスが減り,開発速度の向上に繋がります.
つまり,TypeScriptをもう一歩型安全に近づけるために,TypeScriptで名前的型付けを再現し,idのような共通プロパティを区別出来るようにしたいのです.
構造的に型を区別するTypeScriptにそのような方法はあるのでしょうか?

Branded Primitive

Branded Primitiveという手法を用いることでTypeScriptで名前的型付けを再現することが可能です!
この手法はTypeScriptのgithubのwikiやオライリー・ジャパンから出版されている「プログラミングTypeScript―スケールするJavaScriptアプリケーション開発」(Boris Cherny 著、今村 謙士 監訳、原 隆文 訳)で紹介されており,弊社社内でエンジニア同士のコミュニケーションの際に用いる場合はBrand化と称しています.
number型をBrand化する際には以下のようにします.

type T = number & { readonly brand: unique symbol };
type U = number & { readonly brand: unique symbol };

上記のように,型TUを区別したい場合,それぞれnumber型と互いにプロパティを区別できるオブジェクト型の交差型を定義するのがBrand化です(※1).
このようにするとオブジェクト型の部分が異なることから構造も異なり,TUは互いに異なる型になります.
この時点で名前的型付けを再現出来ているのですが,更にnumber型とオブジェクト型の交差型はnumber型のサブタイプであるため,number型が持つtoStringなどのようなメソッドにも問題なくアクセス出来るのもBrand化のメリットの一つになります.
なお,型TまたはUを持つ値を生成する際には型アサーションが必要となります(※2).
上述のUser型やCounselor型をBrand化すると以下のようになります.

type User = {
    id: number & { readonly brand: unique symbol };
    name: string;
}
type Counselor = {
    id: number & { readonly brand: unique symbol };
    name: string;
}

このようにidの定義にBrand化を適用することにより,無事User['id']型とCounselor['id']型を区別できるようになりました!

Brand化を適用したnumber型の区別

※1
オブジェクト型の部分は互いに区別出来ればどのような形状になっていても構いません.
okunokentaroさんのZennの記事を学習の際に大いに活用させていただいたのですが,その記事で紹介されているジェネリクスを参考に以下のようなジェネリクスを定義するとBrand化の手間が少なくなるかと思います.
ただし,型パラメータTに同じリテラル型を渡してしまうと構造が一致し区別がつかなくなることには注意が必要です.

type BrandedNumber<T extends string> = number & { brand: T };

type User = {
    id: BrandedNumber<'User'>;
    name: string;
}
type Counselor = {
    id: BrandedNumber<'Counselor'>;
    name: string;
}

※2
各所で型アサーションをするのは手間やミスに繋がってしまうため,以下のような生成関数を定義すると良いです.

function UserId(id: number): User['id'] {
    return id as User['id']
}
const userId = UserId(1)

まとめ

本記事では私の実体験を元にTypeScriptをもう一歩型安全にする手法を紹介させていただきました.
TypeScriptは型を区別するための方式に構造的型付けを採用しており,この方式が持つ自由度ゆえに本来意図していない型を利用してしまった場合にもトランスパイラが検出出来ない可能性があります.
Branded Primitiveという手法がTypeScript公式wikiに掲載されており,この手法を区別したい型に対して適用することによって上述のようなミスをトランスパイラが検出出来るようになり,エンジニアのミスを仕組みで解決出来るようになります.
この記事が私と同じようなミスをしてしまった経験のある開発者の方々のお役に立てたら大変嬉しいです.
ここまでお読みいただきありがとうございました!

Androidのヘルスケアアプリ連携について

はじめに

今回は Android アプリ開発において、健康に関するデータを一元管理し、他のフィットネスアプリや健康アプリと連携が行える ヘルスコネクト を用いた開発手法についてまとめたいと思います。
なお、以前 iOS のヘルスケアアプリ連携についてもまとめた記事を公開していますので、iOS 側にもご興味があればぜひ こちら の記事もご覧ください。

ヘルスコネクトとは

ヘルスコネクトは API やライブラリではなく一つのアプリで、健康に関するデータを管理できる新しいプラットフォームとなります。
ヘルスコネクトで健康データを一元管理し、その情報を Google Fit などの健康アプリと連携することができるため、ヘルスコネクトに対応している健康アプリであれば複数アプリ間で簡単にデータを同期することができます。

ヘルスコネクトアプリが端末にインストールされることで、実際に健康データにアクセスする Health Connect API とやり取りするための API サーフェスが提供されるため、データの連携が容易となる仕組みとなっています。
ヘルスコネクトアプリ自体は Google がストアに公開しているもので、 こちら からダウンロードできます。
なお、Android OS 14 の端末の場合は標準でヘルスコネクトアプリがインストールされているので、手動でのインストールは不要です。
※2024/3/13 時点ではまだヘルスコネクトアプリはベータ版となりますので、仕様が変更となる可能性があります。

事前準備

端末にヘルスコネクトアプリをインストールしておいてください。
なお、ヘルスコネクトアプリの Android 要件が OS 9 以上となっていますので、OS 9 以上のデバイスを準備してください。

環境構築・実装手順

では早速、環境構築と実装手順についてまとめていきたいと思います。

開発環境

  • IDE : Android Studio Iguana | 2023.2.1
  • 開発言語 : Kotlin

ライブラリの依存関係を追加

app レベルの build.gradle に以下を追加

dependencies {
    implementation "androidx.health.connect:connect-client:1.0.0-alpha11"
}

ヘルスコネクトクライアントの取得設定を追加

AndroidManifest.xml に以下を追加

<manifest 
    <application
        ...
    </application>

    <queries>
        <package android:name="com.google.android.apps.healthdata" />
    </queries>
</manifest>

取得したい健康データの権限を追加

AndroidManifest.xml に以下を追加します。

<manifest 
    <!-- 歩数の読み取り、書き込み権限 -->
    <uses-permission android:name="android.permission.health.READ_STEPS"/>
    <uses-permission android:name="android.permission.health.WRITE_STEPS"/>

    <!-- 身長の読み取り、書き込み権限 -->
    <uses-permission android:name="android.permission.health.READ_HEIGHT"/>
    <uses-permission android:name="android.permission.health.WRITE_HEIGHT"/>

    <application
        <activity
            <!-- 権限をリクエストする Activity に追加 -->
            <intent-filter>
                <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
            </intent-filter>

</manifest>

uses-permission で読み取り、書き込みをしたい権限を個別に追加、また権限のリクエストを実施する Activity に intent-filter を追加します。
intent-filter を追加することでアプリで権限についての説明画面、及び許可不許可を設定する画面が表示されます。

使用できるデータの型と権限については こちら を参照してください。

ヘルスコネクトアプリがインストールされているかチェック

ここからは実装となります。
健康データにアクセスするためにはヘルスコネクトアプリがインストールされていることが必須のため、インストールチェックを行います。

// インストールされているかチェック
val availabilityStatus =
    HealthConnectClient.sdkStatus(requireContext(), "com.google.android.apps.healthdata")
if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE) return

// インストール済みの場合は Health Connect Client のインスタンスを生成
val healthConnectClient = HealthConnectClient.getOrCreate(requireContext())

インストールされていない場合は、ヘルスコネクトアプリのダウンロードページに飛ばすなどのケアが必要です。

ユーザに権限のリクエストを実施

アプリが適切に健康情報にアクセスすることを明示するため、権限のリクエストを行います。

// AndroidManifest の uses-permission で宣言した内容と同じものを設定
private val PERMISSIONS =
    setOf(
        HealthPermission.getReadPermission(StepsRecord::class),
        HealthPermission.getWritePermission(StepsRecord::class),
        HealthPermission.getReadPermission(HeightRecord::class),
        HealthPermission.getWritePermission(HeightRecord::class)
    )

private val requestPermissionActivityContract =
    PermissionController.createRequestPermissionResultContract()
private val requestPermissions =
    registerForActivityResult(requestPermissionActivityContract) { granted ->
        if (granted.containsAll(PERMISSIONS)) {
            // 全ての権限が許可されたケース
        } else {
            // 許可されていない権限があるケース
        }
    }

private suspend fun requestPermission(client: HealthConnectClient) {
    val granted = client.permissionController.getGrantedPermissions()
    if (granted.containsAll(PERMISSIONS)) {
        // 全ての権限が許可されたケース
    } else {
        requestPermissions.launch(PERMISSIONS)
    }
}

リクエストに成功するとアプリ上で以下のような画面が表示されます。

レコードクラスについて

前項で Permission を指定する際に StepsRecord というクラスを使用していますが、こちらが Health Connect API が提供している歩数のレコードデータを取り扱うクラスとなります。
以降の項目でも紹介をしますが、データを書き込み・読み込みする際は StepsRecord に歩数のデータを設定してデータのやり取りを行います。

なお身長のデータについては HeightRecord を使用するなど、データの種別毎にレコードクラスが用意されています。定義されているレコードについては こちら を参照してください。

データを書き込み

実際にヘルスコネクトに歩数のデータを書き込んでみます。
以下は 2024/3/1 12:00 〜13:00 に 1000 歩歩いたという情報を書き込む例です。

private suspend fun writeStep(client: HealthConnectClient) {
    try {
        val startTime = LocalDateTime.parse("2024-03-01T12:00:00")
        val endTime = LocalDateTime.parse("2024-03-01T13:00:00")
        val zoneOffset = ZoneOffset.systemDefault().rules.getOffset(Instant.now())
        val stepsRecord = StepsRecord(
            count = 1000,
            startTime = startTime.toInstant(zoneOffset),
            endTime = endTime.toInstant(zoneOffset),
            startZoneOffset = zoneOffset,
            endZoneOffset = zoneOffset,
        )
        client.insertRecords(listOf(stepsRecord))
    } catch (e: Exception) {
        // エラーケース
    }
}

上記を実行後、実際にヘルスコネクトアプリを確認すると、本アプリから情報が書き込まれたことが確認できます。

健康データを読み取り

次にヘルスコネクトから歩数のデータを読み取ってみます。
以下は先ほどの項目で書き込んだデータを読み取る例です。

private suspend fun readStep(client: HealthConnectClient) {
    val startTime = LocalDateTime.parse("2024-03-01T12:00:00")
    val endTime = LocalDateTime.parse("2024-03-01T13:00:00")
    val zoneOffset = ZoneOffset.systemDefault().rules.getOffset(Instant.now())
    val request = ReadRecordsRequest(
        recordType = StepsRecord::class,
        timeRangeFilter = TimeRangeFilter.between(
            startTime.toInstant(zoneOffset),
            endTime.toInstant(zoneOffset)
        )
    )
    val response = client.readRecords(request)
    response.records.forEach { record ->
        Log.d("Health Connect Test", "start time = ${record.startTime.atOffset(zoneOffset)}")
        Log.d("Health Connect Test", "end time = ${record.endTime.atOffset(zoneOffset)}")
        Log.d("Health Connect Test", "count time = ${record.count}")
    }
}

上記を実行すると以下のようにログが出力されるため、先ほど書き込んだデータを取得できたことが確認できます。

環境構築・実装手順の紹介は以上となりますが、非常に簡単な実装だけでヘルスコネクト連携ができることが伝わったのでは、と思います。

おわりに

今回はヘルスコネクトを利用した健康データの連携についてさわりの部分を紹介しましたが、最少の工数で手間なく実装ができました。
ヘルスコネクトが公開される以前は Google Cloud で API を有効にする、認証情報を発行するなど手間が多く、不慣れだと実装の前段階でつまりやすく非常に手間がかかるものでしたが、 ヘルスコネクトを利用すればアプリの実装のみに閉じて開発が行えるため、手間も敷居もかなり下がったものと思います。
以前紹介した iOS のヘルスケア連携同様、両 OS とも簡単に実装ができる基盤が整いつつあるため、これを機に両 OS の開発に触れてみてはいかがでしょうか。

今回紹介した内容が少しでも皆さまのお役に立てれば幸いです。

Github Copilot Chat の機能・使い方を整理しつつ開発者体験が向上する活用事例を考えてみた

Github Copilot Chat の機能・使い方を整理しつつ開発者体験が向上する活用事例を考えてみた

はじめに

子育てメディア「トモニテ」でバックエンドやフロントエンドの設計・開発を担当している桝村です。

エブリーは、現在 GitHub Copilot Business を持つ Organization アカウントであるため、多くの開発メンバーが Github Copilot を業務で活用しています。

Github Copilot は、コーディング時に AI ペアプログラマーからオートコンプリート スタイルの候補を提示する拡張機能です。

github.com

Github Copilot のユースケースとして、コーディングにおける補完はもちろん、コメントからのコード・テストの自動生成やコードの説明を求めるなど、様々な場面で活用しています。

今回は Github Copilot Chat について、その基本的な機能や使い方を整理しつつ、開発者体験が向上するような活用事例を考えてみました。

前提

  • 個人、もしくは Organization アカウントで Github Copilot サブスクリプションを持っていること
  • 拡張機能の Github Copilot / Github Copilot Chat がインストールの上、有効化されていること
  • 筆者は VS Code を利用しているため、本記事は VS Code における Github Copilot Chat について記載

環境

Extension version: 0.13.0
VS Code version: Code 1.87.0

Github Copilot Chat とは

Github Copilot Chat は、Github Copilot の拡張の一つであり、Github Copilot との対話を可能にするチャットインターフェースです。

Copilot Chat により、コーディング関連の問い合わせをしたり、回答を得ることが可能です。

docs.github.com

2024 年 1 月 9 日に IDE の Visual Studio CodeVisual Studio 向けに一般提供 (GA) を開始し、OpenAI の GPT-4 をベースにした自然言語処理モデルを利用してます。

Github Copilot Chat における質問をより効果的・効率的にする 3 つの機能

Github Copilot Chat では、質問をより効果的・効率的にするために、以下の 3 つの機能を提供しています。

  • エージェント
  • スラッシュコマンド
  • コンテキスト変数

これらの機能は単体もしくは組み合わせて利用することで、より効果的・効率的な質問や回答を得ることができます。

code.visualstudio.com

エージェント

エージェントとは、特定の領域に特化した回答を生成できる AI エージェントのことです。

入力フォームに対して、「@(アットコマンド)」を使ってエージェントを指定することができます。

エージェント 説明
@workspace ワークスペース内のコードやファイルについて回答
@terminal 統合ターミナルに関するコンテキストについて回答
@vscode エディタ (VS Code) 自体のコマンドや機能について回答
使用例:Go のプロジェクトで利用されている主な技術スタックについて尋ねる
@workspace このプロジェクトで利用されている主な技術スタックは何ですか?

エージェントの利用例

スラッシュコマンド

スラッシュコマンドとは、Copilot がより適切な回答を提供できるように、特定のアクションを実行するためのコマンドです。

特定のエージェント「@workspace」 「@vscode」を前提にしているコマンドもあり、その場合、エージェントを省略して実行できます。ex. @workspace /explain とするところを /explain だけで実行可能

入力フォームに対して、「/(スラッシュ)」を使ってスラッシュコマンドを実行することができます。

@workspace に対するスラッシュコマンド
スラッシュコマンド 説明
/doc ドキュメントのコメントを追加
/explain コードの動作を説明
/fix 選択したコードの問題に対する修正を提案
/generate 質問に回答するるコードを生成
/optimize 選択したコードの実行時間を分析して改善
/tests コードの単体テストを作成
/new 自然言語の説明に基づいて新しいプロジェクトを作成
@vscode に対するスラッシュコマンド
スラッシュコマンド 説明
/api VS Code の拡張機能に関する回答を生成
/search VS Code の検索機能により、ワークスペース内のコードやファイルを検索
エージェント共通のスラッシュコマンド
スラッシュコマンド 説明
/clear チャットをクリア
/help Github Copilot Chat のヘルプを表示
使用例:サーバーサイドエンジニアにおすすめの拡張機能を教えてもらう
@vscode /api サーバーサイドエンジニアにおすすめの拡張機能を10個挙げてもらえますか?

スラッシュコマンドの使用例

コンテキスト変数

コンテキスト変数とは、質問時に渡したい追加の情報 (コンテキスト) を指定する変数です。

入力フォームに対して、「#(シャープ)」を使ってコンテキスト変数を指定することができます。

コンテキスト変数 説明
#selection エディタの選択箇所
#editor エディタの表示領域
#file:<ファイル名> 選択したファイル
#terminalSelection ターミナルの選択箇所
#terminalLastCommand ターミナルで最後に実行したコマンドと結果
使用例:yarn dev でローカルでサーバーを起動した後、そのコマンドとその結果を説明してもらう

コンテキスト変数の使用例1

@terminal #terminalLastCommand

コンテキスト変数の使用例2

Github Copilot Chat を利用する 3 つの UI

Github Copilot Chat へ質問をする際、以下の 3 つの UI を利用することができます。

  • Chat View
  • Quick chat
  • Inline chat

これらの UI は、それぞれの特性によって、使い分けることができます。

Chat View

Chat View とは、VS Code のサイドバーに表示されるチャットビューです。

大小を問わず、質問に対して AI によるサポートを受けることができます。

アクティビティバーからチャットビューにアクセスするか、⌃⌘I キーバインドを使用します。

chat view

Quick chat

Quick chat とは、エディタの上部に表示されるチャットビューです。

完全なチャットビューセッションを開始したり、エディタでインラインチャットを開くことなく、簡単に質問をすることができます。

コマンド パレットで Chat: Open Quick Chat を実行するか、キーボード ショートカット ⇧⌘I を使用します。

quick chat

Inline chat

Inline chat とは、コード上に表示されるチャットビューです。

コーディング中にインラインで質問をすることができます。

どのファイルでも、キーボードの ⌘I を押すと、Copilot インライン チャットを表示できます。

inline chat

開発者体験が向上する活用事例 (ワークスペース編)

プロジェクトを新規作成してもらう

自然言語の説明に基づいて新しいプロジェクトを作成してもらいます。

@workspace /new FizzBuzz 問題を標準出力する Golang プログラムとテストコードを作成

また、ルートディレクトリに Golang プログラムを呼び出す main.go と go.mod 、README を作成してください

プロジェクトの新規作成1

プロジェクトの新規作成2

結果として、質問した通りのディレクトリ構造やファイルが作成されました。

また、概ね期待通りのプログラムが生成され、エラーなく実行できることを確認しました。

特定の指示による既存のコードの修正・差分表示・一括置換してもらう

Copilot Chat に作成してもらった FizzBuzz 問題のプログラムに対して、エラーハンドリングを追加してもらいます。

エラーハンドリングを追加してください

コードの修正・置換

概ね期待通りの修正が行われました。

また、差分表示では、修正前と修正後のコードの違いが色分けされて表示され、修正箇所が一目でわかりました。

選択したコードを説明してもらう

選択したコードを日本語で説明してもらいます。

/explain in Japanese

コードを説明1

コードを説明2

外部パッケージの処理の概要のみでなく、ソースコードについても丁寧に説明してもらうことができました。

また、色分けや改行がとても見やすく、コードの理解を助けてくれました。日本語も特に違和感ないですね。

コードのエラーや問題点への修正を提案してもらう

選択したコードのエラーや問題点への修正を提案してもらいます。

事前に FizzBuzz 問題のプログラムに対して、エラーを 5 つ追加しておきます。

エラーを追加

@workspace /fix #file:fizzbuzz.go

エラーを修正

一つの要求に対して、漏れなく全てのエラーに対する修正を提案してくれました。

各修正に対する説明も丁寧だと感じました。

開発者体験が向上する活用事例 (ターミナル編)

実行したコマンドがエラーだった場合、修正を提案してもらう

直前のコマンドがエラーだった場合、修正を提案してもらいます。

事前に FizzBuzz 問題のプログラムに対して、エラーを追加した上で、ターミナル上でコマンドを実行します。

すると、ダイアログに Explain using Copilot が表示されるので、それをクリックします。

ターミナルからエラーを修正1

@terminal #terminalLastCommand

ターミナルからエラーを修正2

コマンドの実行結果を踏まえて、修正を提案してもらうことができました。

補足ですが、#とタイプすれば、#terminalSelection が補完されるので、コンテキスト変数は覚えなくても良さそうでした。

CLI コマンドを教えてもらう

活用事例の最後になりますが、直接プロンプトで CLI コマンドを教えてもらいます。

@terminal このワークスペースを git で管理したい

ターミナルからコマンドを聞く

期待通りのコマンドを教えてもらうことができました。

また、サジェストされた結果が気に入ったら、ターミナルのアイコンをクリックすればコマンドの内容がターミナルに貼り付けられるようでした。

Github Copilot Chat を使って感じたメリットや比較

個人的には、Github Copilot Chat を使うメリットとしては、ChatGPT と比較して vscode などの IDE 内で開発が一定のところまで完結できることだと感じました。

画面の切り替えや ChatGPT ↔︎ エディタのコピペをする必要がないため、開発効率が向上する可能性があると思います。

とはいえ、 GPT-4-turbo をはじめとした他の対話型 AI サービスの方が、回答の精度や記憶力の点で優れている といった点も十分に考えられるため、適材適所に使い分けることが重要だと感じました。

他のメリットとしては、Github Copilot と比較して特定の指示による既存のコードの修正・一括置換ができたり、ワークスペース内のコードに関する質問のみでなく技術的な質問をしやすいいったところだと感じました。

おわりに

今回は、Github Copilot Chat について、その基本的な機能や使い方を整理しつつ、開発者体験が向上するような活用事例を考えてみました。

Copilot Chat は、まだまだ機能が追加されていく可能性があるため、今後のアップデートにも期待したいと思います。

本記事が Github Copilot Chat を利用される方々の参考になれば幸いです。

Redash運用環境改善の取り組み

はじめに

こんにちは。 株式会社エブリーの開発本部データ&AIチーム(DAI)でデータエンジニアをしている吉田です。

今回は、半年ほど前に実施した挑戦Week内で行ったRedashの運用環境整備について紹介します。
DAIでは、BIツールとしてRedashをEC2で運用していましたが、運用コストの削減と運用の効率化を目的にECSへの移行を実施しました。  

背景

これまではEC2のdocker上でRedashを運用していましたが、以下のような問題がありました。

  • infra周りが管理されていない
  • docker-compose.ymlが管理されていない
  • コンテナがメモリを食いつぶして、サービスが落ちることがある
  • 障害対応、バージョンアップ、ライブラリの追加などの運用が大変

特にライブラリの追加などでRedashに変更を加えたい場合、scpでファイルを転送しsshでログインしてコマンドを実行する、などの煩雑な作業が必要でした。
手作業なためミスが起こり得る状況のほか、それらの変更の履歴が残らないなどの問題からRedashの運用環境の改善を行うことにしました。

そこで、IaCによる環境構築、CI/CDの導入、運用の効率化を目的にECSへの移行を実施しました。

ECSへの移行

ECSへの移行により、以下のような構成に変更しました。

  • IaC
    • terraformによるRedashの環境管理
  • CI/CD
    • AWS CodeBuildによるRedashのビルド/デプロイ
    • ecspressoによるECSサービス/タスク定義のデプロイ
  • Redashの運用
    • ECSによるBlue/Greenデプロイ
    • ECRによるRedashのコンテナイメージの管理

redash-infra

ecspressoによるECSへのデプロイ

ecspressoは、ECSのタスクやサービス定義の管理/デプロイを行うためのツールです。 ecspressoを利用することで、ECSのタスクやサービス定義をjsonで管理し、コマンド一つでデプロイが行えるようになります。 ecspresso.yml

region: ap-northeast-1
cluster: redash-fargate
service: redash-server
service_definition: ecs-service-def.json
task_definition: ecs-task-def.json
timeout: "10m0s"

ecs-service-def.json

{
  "deploymentConfiguration": {
    "deploymentCircuitBreaker": {
      "enable": true,
      "rollback": true
    },
    "maximumPercent": 200,
    "minimumHealthyPercent": 100
  },
  "deploymentController": {
    "type": "ECS"
  },
  "desiredCount": 1,
  "enableECSManagedTags": false,
  "enableExecuteCommand": true,
  "healthCheckGracePeriodSeconds": 0,
  "launchType": "FARGATE",
  "loadBalancers": [
    {
      "containerName": "redash-server",
      "containerPort": 5000,
      "targetGroupArn": "arn"
    }
  ],
  "networkConfiguration": {
    "awsvpcConfiguration": {
      "assignPublicIp": "ENABLED",
      "securityGroups": [
        "sg-"
      ],
      "subnets": [
        "subnet-",
        "subnet-"
      ]
    }
  },
  "pendingCount": 0,
  "platformFamily": "Linux",
  "platformVersion": "LATEST",
  "propagateTags": "NONE",
  "runningCount": 0,
  "schedulingStrategy": "REPLICA",
  "tags": [
    {
      "key": "Service",
      "value": "redash"
    },
    {
      "key": "Terraformed",
      "value": "1"
    }
  ]
}

ecs-task-def.json

{
  "containerDefinitions": [
    {
      "command": [
        "server"
      ],
      "cpu": 0,
      "secrets": [
      ],
      "environment": [
      ],
      "essential": true,
      "image": "ecr.image",
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-create-group": "true",
          "awslogs-group": "/ecs/redash",
          "awslogs-region": "ap-northeast-1",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "name": "redash-server",
      "portMappings": [
        {
          "appProtocol": "",
          "containerPort": 5000,
          "hostPort": 5000,
          "protocol": "tcp"
        }
      ],
      "ulimits": [
        {
          "hardLimit": 65536,
          "name": "nofile",
          "softLimit": 65536
        }
      ]
    }
  ],
  "executionRoleArn": "arn",
  "taskRoleArn": "arn",
  "family": "redash-server",
  "ipcMode": "",
  "cpu": "2048",
  "memory": "4096",
  "ephemeralStorage": {
    "sizeInGiB": 30
  },
  "networkMode": "awsvpc",
  "pidMode": "",
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "tags": [
    {
      "key": "Service",
      "value": "redash"
    },
    {
      "key": "Terraformed",
      "value": "1"
    }
  ]
}

タスク定義、サービス定義をjsonで管理し、ecspresso.ymlで定義した内容を元に、ecspresso deploy --config ecspresso.ymlでデプロイを行うことができます。
また、diffを出力することもできるため、変更点を把握しやすくなります。

移行後

CI/CD整備により、masterブランチへのマージをトリガーにRedashのビルド/デプロイが自動で行われるようになりました。 これにより、ライブラリ追加などの変更が発生した際に、手作業でのデプロイ作業が不要となりました。 またecspressoによるECSへのデプロイにより、ECSのタスクやサービス定義の管理が容易になり、diffを出力することで変更点を把握しやすくなり意図しない変更を防ぐことができるようになりました。

最後に

今回はRedashの運用環境の改善を行いました。 EC2での運用からECSへの移行、CI/CDの導入、IaCによる環境管理などを行い、運用コストの削減と運用の効率化を実現しました。