mitsuのぶろぐ

基本的にはプログラミングの話のつもり。

「目標」が嫌い。私はあえて「仮説・検証」でいきたい

とあるブログを読み、ハードだった目標を頑張って達成した、みたいなものを見かけました。

それ自体はとてもいいことだと思いますし、否定することはなにもありません。

ただ自分は昔から、そして今でも「目標を何がなんでも達成する」みたいな雰囲気とかが好きにはなれません。

きっと嫌いな理由は以下です。

  • モノによっては過度なプレッシャーとなること
  • その目標が「短期的」「近視眼的」ではないかという観点

過度なプレッシャー

人間は元来何かしらの「枠」がないと怠けてしまうものです。

夏休みの宿題はギリギリ(やらねければいけないと感じるタイミング)にならないとやらないですし、「目標」や「何かしらの前提条件」など限られたスコープとリソースの中で如何に対応するかで爆発的なソリューションを生み出すことがあると思います。

プレッシャーもそのスパイスの一つです。自分自身、そのスパイスがなければ発火しないタイプです。

そんなスパイスが理不尽でない限りはわりと降りかかっても嫌いではありません。

でも自分のチームやメンバーとかに過度にプレッシャーがかかるのはあまり好きにはなれません。

エンジニアリングならではの話

ここまで書いてて追加で思ったこととして、とくにエンジニアリングの領域ならなおのことそう感じてしまうのかもしれません。

エンジニアリングにおけるアクションはとても費用対効果がわかりにくいものだと思っています。

極端な例ですがソースコード一行変えるだけで売上が倍になることもあれば、半分になることもあります。

そしてその「一行の変更」に対してものの数分でたどり着くこともあれば、一月かかることもあるかもしれません。

1ヶ月で作ったプロダクトがものすごい売上をたてることがあれば、半年かけて作った機能が全然売上貢献しないかもしれません。(若干施策がミスってる可能性もありますが、例えば法改正に対する対応とかは直接は数字に作用しないかもしれません)

これらは構造上仕方のないことなので、理解はしています。

ただプレッシャーみたいなものが必要になった際に、メンバーのリテンションや事業的な貢献度、そしてそもそもプレッシャーをかけることによる効用のバランスが本当に適切なのかが未だによくわかっていないところはあります。

その目標が「短期的」「近視眼的」ではないかという観点

上の内容である程度言及してしまいましたが、エンジニアリング観点のでアプローチの是非というのが結構可視化しにくい側面があると思っています。

そのためそのタイミングの目標に対して頑張ったのにもかかわらず「今月は達成したけど来月以降はだめ」みたいな状態になったときに「なんのために頑張ったのだろうか」という気持ちになったりするリスクがあるのかなと思っています。

言うなれば組織としての目標の目線や温度感と現場の実働と実感のギャップに起因するところかもしれません。

もちろんそこのバランスを取るというのがマネージャーの仕事なのかもしれませんが。


さて、とやかく言いましたが改めて「目標設定」も「プレッシャー」も適切にある分には有用ですし、組織として進んでいく上では大事なものです。

ただ個人的な向き合い方として目標はもちろん大事だけど、その目標を盲目的に目指すのではなく目標に向かって進めているか、もしくは目標そのものが適切かを見極めれるよう仮説検証、検査と適応ができてるかをベースに考えたいと思いました。

仮説検証、検査と適応

ただ単純に目標達成するだけでいいならがむしゃらに目標に向かってぶつかっていけばいいと思いますが、個人的にやりたいことはそうではありません。

どちらかというとチームや各個人に対して「より高度な仮説を立てれる」ようになってもらったり、「より高度に検証や実現」をしてほしいと思っています。

そのため目標そのものに対して必要以上にプレッシャーを感じるよりも、

  • 目標に向かうための日々の仮説をきちんと立てる
  • 立てた仮説を短いスパンで検証、ふりかえりをする

というところに対して適度にプレッシャーを感じてもらったり、こだわりを持って取り組んでほしいと思っています。

上記を通じてうまく目標を達成したのであれば評価もできるし、うまくいかなかったとしてもあがったであろう仮説の精度や検証力や実行力も無駄になることはありません。むしろその達成ができなかった経験を踏まえ、次の目標を適切にたてれるかもしれません。

いろいろな観点はあると思いますが、私としては検査と適応を主軸にこのあたりアプローチしていけたらなと改めて思いました。

Drizzleをちょっと触ってみた

orm.drizzle.team

TypeScriptのORMとしてTypeORMやPrismaとの対抗馬としてあるのがDrizzle。

どんなものか気になったのでちょっと触ってみました。

// schema.ts
import {
  integer,
  pgEnum,
  pgTable,
  serial,
  uniqueIndex,
  varchar,
} from "drizzle-orm/pg-core";

// declaring enum in database
export const popularityEnum = pgEnum("popularity", [
  "unknown",
  "known",
  "popular",
]);

export const countries = pgTable(
  "countries",
  {
    id: serial("id").primaryKey(),
    name: varchar("name", { length: 256 }),
  },
  (countries) => {
    return {
      nameIndex: uniqueIndex("name_idx").on(countries.name),
    };
  }
);

export type Countries = typeof countries.$inferSelect;

export const cities = pgTable("cities", {
  id: serial("id").primaryKey(),
  name: varchar("name", { length: 256 }),
  countryId: integer("country_id").references(() => countries.id),
  popularity: popularityEnum("popularity"),
});
// src/index.ts
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import { countries } from "../db/schema";

const queryClient = postgres(process.env.DATABASE_URL || "");

const db = drizzle(queryClient);

const main = async () => {
  const now = new Date();
  const result = await db.transaction(async (tx) => {
    await tx.insert(countries).values({ name: now.getTime().toString() });

    return tx.select().from(countries);
  });
  console.log(result);
};

main().catch((error) => {
  console.error("Drizzle: Error occurred:", error);
});

install

以下を参考にしました

orm.drizzle.team

他のライブラリでpostgres使う場合 pg を利用することが多い印象なので、 pg 使ってもよいかなと思いました。

orm.drizzle.team

setup schemas

// ./drizzle/schema.ts

import {
  integer,
  pgEnum,
  pgTable,
  serial,
  uniqueIndex,
  varchar,
} from "drizzle-orm/pg-core";

// declaring enum in database
export const popularityEnum = pgEnum("popularity", [
  "unknown",
  "known",
  "popular",
]);

export const countries = pgTable(
  "countries",
  {
    id: serial("id").primaryKey(),
    name: varchar("name", { length: 256 }),
  },
  (countries) => {
    return {
      nameIndex: uniqueIndex("name_idx").on(countries.name),
    };
  }
);

export type Countries = typeof countries.$inferSelect;

export const cities = pgTable("cities", {
  id: serial("id").primaryKey(),
  name: varchar("name", { length: 256 }),
  countryId: integer("country_id").references(() => countries.id),
  popularity: popularityEnum("popularity"),
});

例題コードを参考にして書きましたが、実際に使う場合はもう少し書き方を意識したいなと思いました。

具体的にはクリーンアーキテクチャでいうところのインフラストラクチャの層にきちんと閉じ込めたいと思いました。

理由としては export const countries = pgTable("countries", ...) と指定しているところがそのままDrizzleのclient部分に刺す必要があり、単にmigration用のschemaとして独立しているだけでなく、Entityのような振る舞いをするからです。

src/index.ts

import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import { countries } from "../db/schema";

const queryClient = postgres(process.env.DATABASE_URL || "");

const db = drizzle(queryClient);

const main = async () => {
  const now = new Date();
  const result = await db.transaction(async (tx) => {
    await tx.insert(countries).values({ name: now.getTime().toString() });

    return tx.select().from(countries);
  });
  console.log(result);
};

main().catch((error) => {
  console.error("Drizzle: Error occurred:", error);
});

今回はあくまでやってみた、的なやつなので index.ts にまとめて書いてみました。

簡単にtransactionも書いてみましたが、特に違和感がある感じではありませんでした。

雑感

深いところまで探索できてるわけではないのであくまでちょろっと触った感想は以下になります。

  • セットアップはわりとラクに感じたい
  • schemaをts(型がある状態)でかけるのは個人的に好み
  • Drizzle自身が言ってるとおり、clientの書き味がSQL感があって、興味深かった
  • schemaの定義の部分で id: serial("id").primaryKey() という書き味が左辺にも右辺にも id 書かれてあって悩ましい気持ちになった
  • pgTable という形でmigrationのschemaとしても使いつつ、clientのschemaとして利用するのは後々好みが分かれそう
  • schemaを更新すると差分のSQLを生成してくれるということで、このあたりは(自分が記憶してる限り)TypeORMにはなく、Prismaのような振る舞いだなと思った

また時間があればもう少し深堀りしたいなと思いました。

チームが安定するまでは必要以上にふりかえりでTryを追い求めない、という話

スクラムアジャイルにおいてふりかえりはとても重要で、特にふりかえりをするにあたってよりよい状態を目指すことが一つ大きなポイントだと思います。

毎週 1% の改善を 1 年間(52 週)続けると、年末には約 67.77% の改善結果になることがわかります。1 年の間に 1% の積み重ねによって、元の状態の 1.68 倍ほどの改善が達成されることになります。

ただ、必ずしもTryを出すことに躍起になる必要もないのかなと思ったので、そのあたりの話を書きたいと思います。

個人的には特にチームができはじめたばかりや、何かしらの大きな変化があり、安定を目指しはじめたばかりの状態などではTryを求めないようにしているなと思いました。

motifyhr.jp

(引用すべきかは悩みどころですが)タックマンモデルを引用すると、個人的に形成期や混乱期のときなどは必要以上にTryをひねり出したりしなくてもよいかと思います。

その代わりに

  • 各々が困ってること、気になってることを遠慮なく出し、まずはそれをシェアする
  • 各メンバーの困っていることや気になってることを見て価値観や目線をあわせる

といったことを意識しています。

考え方や価値観などの認識合わせや「◯◯さんはこういう人、こう考える人」といったところを合わせた上で具体的なHowの話などをしてしまうと正論で殴り合うことなども考えられます。

まずはお互いを受け入れるところからはじめ、「チームとして困っている、どうありたいか」を考えれる土壌を形成するとよいのかなと思っています。

knipを触ってみた

なんとなく存在は知っていた「より高度に利用してない変数やファイルを検知してくれる」knipをさわってみたので備忘録まで。

knip

knip.dev

レポートしてくれる内容は以下のようです

knip.dev

実際に試してみた

knip.dev

// src/main.ts
import { usedFunction } from './utils';
import { UsedInterface } from './types';
import './side-effects';

function main() {
    const data: UsedInterface = {
        id: 1,
        name: 'Test'
    };
    console.log(usedFunction(data));
}

main();

// src/utils.ts
export function usedFunction(data: { name: string }): string {
    return `Hello, ${data.name}!`;
}

export function unusedExportedFunction(): void {
    console.log("This function is exported but never used.");
}

function unusedPrivateFunction(): void {
    console.log("This function is neither exported nor used.");
}

// src/types.ts
export interface UsedInterface {
    id: number;
    name: string;
}

export interface UnusedInterface {
    title: string;
    description: string;
}

export type UnusedType = 'A' | 'B' | 'C';

// src/unused.ts
export const unusedConstant = 42;

export function anotherUnusedFunction(): number {
    return Math.random();
}

// src/side-effects.ts
import { dynamicImport } from "./dynamic-import";

console.log("This file is imported for side effects");

const privateUnusedVariable = "I'm not exported or used";

dynamicImport();


// src/indirectly-used.ts
export function indirectlyUsedFunction() {
    console.log("This function is used indirectly");
}

// src/dynamic-import.ts
export async function dynamicImport() {
    const module = await import('./indirectly-used');
    module.indirectlyUsedFunction();
}

上のコードを用意してknipを実行してみました。

src/unused.ts は Unsed fileとして検出されました。すごい。

他もexportsが使われてないと教えてくれました。

src/dynamic-import.tsconst module = await import('./indirectly-used'); もimportとして認識されておりこれまたすごいなと思いました。

CIに組み込んでもよさそう

余談

ts-pruneというをなんとなく知っていたのですが、見てみたところ

Please use knip which carries on the spirit.

と書かれてたのでknipを使うのがよさそうですね。

github.com

どのような視点からふりかえりを行うか

毎週行っているレトロスペクティブから学びがあったので書き出しておきます。

いつもは

最近自分が行っていたふりかえりでは「Good Bad」のフレームワークを少し変えたものを使っています。

「気になること/変えたいこと」と「Good」にの二項目を用意し、「気になること/変えたいこと」の比重を高くふりかえりを行うようにしています。

わりと頻度高くこのふりかえり手法で行っているのですが、さすがに飽きてくるよなというところがあったので、少しやり方を変えてみました。

やってみたこと

「どのようなチームになりたいか / どのようにことにチャレンジしたいか」-> 「どのように解決できそうか」という項目に変えて実施してみました。

いつもは少しネガティブな要素を列挙し、その課題を解消するという形でチームをよりよい方向へ持っていくようにしていましたが、今回はポジティブな側面からチームをよい方向にもっていくことを検討してみました。

結果

実施してみた結果として

「どのようなチームになりたいか / どのようにことにチャレンジしたいか」でHowの要素もちょいちょい出てしまい、「どのように解決できそうか」の列挙は実際にやらず「どのようなチームになりたいか / どのようにことにチャレンジしたいか」の付箋を書き出したあとすぐ深堀りをするようになりました。

このあたりの書き方みたいなものはもう少しどのように書いたらよいかをシェアする必要があるなと思いました。

しかしながら、どうなりたいか、どうしたいかはしっかり書いてもらうことでき、「どういう課題を解消したいか」といった観点からではでないネタがたくさん出てきたのでとてもよかったと思います。

ただし、今後毎回やっていきたいかというとちょっと悩ましいなと思いました。

毎週のようにこの項目でやると、以下のようになるのではないかと考えています。

  • 少し先の未来について語る内容であるため、簡単に新しいものが出ることが少なく、内容が同質化する
  • 内容の同質化により、同じ話ばかりして停滞感を感じやすい

そのため、基本はいつも実施している課題感からtryを抽出していくフレームワークでやりつつ、月に一回くらい?などの頻度で少し先を見据えたり、理想の状態から考えるふりかえりをするとバランスがよいのかなと思いました。

組織変更がもたらす影響の言語化をしてみた

大なり小なり組織が変化するということはあると思います。

身近なところでもそんなことが起きており、その際に起きる悪影響って具体的にどういうことがあるんだろうかというのが改めて気になったので言語化してみたいと思いました。

前提として

この記事では少し大きな事象としてソフトウェア開発におけるチームの統廃合や異動など、ある個人に対して今まで行っていたことや場所から大きく変わる状態を想定しています。

変わる項目

ざっくり以下のようなものが変わるのかなと思いました。

  • 日々の作業の変化(今までとの変化)
  • 関わり方、振る舞いの変化
    • 人間関係
    • チームの文化

必ずしも上記の全部が変わるとは思いません。

一方で上記の全部が変わるとなると組織内の異動や変化であっても実質転職に近しいレベルの変化かもしれません。

もしある程度変わるものがコントロールできるのであれば、日々の作業か関わり方のどちらかからちょっとずつ変化させていくということができるの、ストレスが低く馴染んでいけるのかもしれません。

上の変化から転じて

ある程度変化を望んでいた、もしくは変化することがそこまで苦痛ではないとかであれば特段問題はないですが、組織的にトップダウンでの変化や、コロコロ状況が変わってしまうといったことがある場合は以下のようなことを思うかもしれません。

  • キャリアに対する不安
    • このまま安定して働けるのか
    • 自分の成長につながるか

端的にいうと職場に対する不安や疑念、といったものでしょう。

安定して個人が納得できることができないとなると、その場にいていいのか、別の場所でチャレンジしたほうがいいのでは?と思うことも出てくるでしょう。

大きな変化がある場合はキャリアや中長期の目線合わせなども忘れずに行う必要もあるのかもしれません。

上のような内容を踏まえて組織的な変化があるときには個々人と丁寧にコミュニケーションをする必要がありそうです。

prismaで複数のDBにアクセスしたい場合

prismaでは2024/09時点で複数のDBに対応する場合の正式な実装方法はないようです。

上記に対するワークアラウンドがあったので、そちらについてメモしておきたいと思います。

github.com

基本的には上記にある内容です。

ディレクトリ構成

以下のような状態になっているとよいかと思いました。

.
├── prisma/
│   ├── db1/
│   │   └── schema.prisma
│   └── db2/
│       └── schema.prisma
└── package.json

/prisma 配下にschema.prisma を配置してしまうと、prisma直下にdb1、db2それぞれのmigrationsが作られ、コンフリクトしてしまうおそれがあるため、一階層掘り、その中にshema.prismaを配置したほうがよいかと思います。

prisma migrate を実行すると以下のような状態になると思います。

.
├── prisma/
│   ├── db1/
│   │   ├── schema.prisma
│   │   └── migrations
│   └── db2/
│       ├── schema.prisma
│       └── migrations
└── package.json

schema.prisma に対する対応

datasource db {
  provider = "postgres"
  url      = env("DB1_URL")
}

generator client {
  provider = "prisma-client-js"
  output   = "./generated/client1"
}
  • url = env("DB1_URL") の部分には利用したいDBのDATABASE_URLを指すようにしましょう
  • output = "./generated/client1" ここが一つ大きなポイントで、本来であれば型を生成すると自然とnode_module内に展開され、 import { PrismaClient } from "@prisma/client"; といった形でimportできるのですが、この形を取ってしまうと一つのschemaしか参照できないため、型の生成場所を指定します

コード生成時

prisma generate --schema prisma/schema1.prisma

といったかんじでどのschemaの情報を参照するか指定しましょう。

コード内での書き方

import { PrismaClient as PrismaClient1 } from '../prisma/db1/generated/client1'
import { PrismaClient as PrismaClient2 } from '../prisma/db2/generated/client2'

const client1 = new PrismaClient1()
const client2 = new PrismaClient2()

上に書いた通り、本来であれば

import { PrismaClient } from "@prisma/client";

といったimportの仕方をしますが、今回はschema内でoutputとして指定した箇所からimportするようにしましょう。

細部の書き方はいろいろあると思いますが、上記の内容をベースに用意すれば複数のDBschemaの管理ができるかと思います。