TECH PLAY

プログラミング

イベント

マガジン

技術ブログ

はじめに こんにちは、セーフィーのたかぎです。 新卒エンジニア向けの研修プログラムをより充実させたいと思いつつも、新しい講座を通常業務の傍らで一から用意するのはハードルが高いものです。 今回、Claude Codeを活用することで、これまで手が出せなかった新しい教材の作成に踏み切ることができました。 この記事では、Claude Codeを使って新卒研修の教材を実際に作った過程と、そこで得た知見を共有します。 なぜ今回できたのか 結論から言うと、生成AIの性能向上により教材のハードルが大幅に下がったからです。 単純に教材を1から作っていくとなると記述量が膨大になります。 教材を自前
はじめに こんにちは、プラットフォーム部の 勝間田 です! 今回は書籍紹介記事の第2弾です! 昨年投稿した第一弾の記事は👇にあります。 tech.stmn.co.jp 今回はそれぞれ職種の異なる4人が各々GWで読んでよかった書籍について、紹介させていただきます! この記事で何か学びになったり、書籍を読むきっかけになったら嬉しいです! SREの知識地図—⁠—基礎知識から現場での実践まで 勝間田が紹介する本は、「SREの知識地図」という書籍です! gihyo.jp 私自身、今年からSRE業務に携わることになったため、SRE関連の書籍を探していたところこの本に出会いました! 読んでみて、SLIやSLO、The Four Golden SignalsといったSRE業務でよく使われる用語の意味をしっかりと学習することができました。用語の意味を理解したことで、普段何気なく活用していたDatadogなどの監視ツールも、より使いやすくなった気がします。 また、この書籍を読んでからシステムの「攻めと守り」や、エラーバジェットについて意識するようになりました。弊社ではまだ明確にSLO等を定義できているわけではないので今後の課題ですが、意識づけができたのはよかったかなと思っています! さらに、普段チームで行っているSRE業務が正しく行えているかの「答え合わせ」ができたのもよかったです。例えば、障害発生時に作成しているポストモーテムについては、本に記載されている通り「再発防止」や「被害の最小化」に向けた具体的なネクストアクションまで落とし込めていることが確認できたので、これは今後も自信を持って継続していきたいです。 弊社ではAIを活用すべく、開発チームの形も大きく変わりました。 本書ではチームトポロジーについても触れられており、4つのチームタイプと3つの主要なインタラクションモードが図解でわかりやすく解説されていました。色々なパターンのSREについて知ることができたので、自組織にあった動きができるよう精進していきたいです! SRE業務を始めることになり、SLIやSLOなどの基礎用語からしっかり理解したい方におすすめです! エンジニアリング組織論への招待 こんにちは、名古屋でEMをしているあさしん( @asashin227 )です。 私がお勧めするのは、「エンジニアリング組織論への招待」です。 gihyo.jp 日々エンジニアリングの現場で直面する様々な不合理に対して『エンジニアリング組織論への招待』は、ビジネスや組織、コミュニケーションの構造的な課題として捉え直し、どのように向き合うべきかを説明しています。 この本を読んだことで「不確実性をいかに最小化するか」、「不確実性を受け入れたまま、いかに前に進めるか」という視点を知ることができました。 プロジェクトを進める中で、「ここが分からないので進めません」「経験がないから難しいです」といったメンバーからの相談を受けることがあります。 本書を読んだことで、未知の領域に対しても「次に進むための構造的な視点」を持って向き合えるようになりました。 現代はAIの進化により、実装方法(How)に頭を悩ませるシーンが劇的に減りました。今私たちが集中すべきなのは、「どのような課題を解決し、どのような価値を創るのか」という本質的な問いです。 このような時代だからこそ、「未知を既知に変えていくプロセス」そのものの重要性が増しています。自分の知らない領域に飛び込むことを恐れず、仮説を持って挑戦し続ける。このマインドセットこそが、技術力以上に求められる現代の重要なソフトスキルではないでしょうか。 本書は、組織論の解説書ではなく、不確実なこのAI時代でエンジニアとして、もしくはリーダーとして「いかに思考し、行動するか」のマインドセットの下地を与えてくれる一冊です。 現状に閉塞感を感じている方や、新しい挑戦に踏み出す勇気が欲しい方に、ぜひ手に取っていただきたいです。 デザインの伝え方 はじめまして、プロダクトデザイナーの hikky です。 最近はコードを書く機会も増えてきたので、エンジニアによる書籍紹介に混ぜてもらいました🐢 私が紹介するのは、オライリー「 デザインの伝え方 」です。 www.oreilly.co.jp AIエージェントの発展でデザイン領域に踏み込むエンジニアの方も増えてきましたが、デザインに直接関わらない場合でも、自分が書いたコードに承認をもらう場面は誰にでもあると思います。そこで切っても切り離せないのが「コミュニケーション」です。本書はその根っこにある考え方を学べる一冊となっています。 本書が教えてくれることはシンプルで、コミュニケーションにおいて「 聞く・伝える・信頼を築く 」がいかに大事か、ということです。文字にすれば当たり前なのですが、その当たり前が一番難しい。 たとえば「聞く」一つとっても、相手に「ちゃんと自分の話を聞いてくれている」と感じてもらえているか、スムーズに本音を引き出せているか、といった観点があります。「伝える」についても、専門用語は同じ知識を持つ人同士では効率の良い言葉ですが、そうでない相手にはノイズになり得ます。立場の違う相手にどう届けるかという視点が必要なのです。 本書を通じて、エンジニアやビジネスサイドのメンバーは自分と違う立場で物事を見ており、それを前提に意図を汲み取ろうというマインドが強くなりました。違うからこそ、お互いを尊重して歩み寄ることがコミュニケーションには欠かせません。AIがどれだけ進化しても、人と人との対話は変わらず残り続けます。 他職種のメンバーともっとうまく連携して、良いものを届けていきたいと感じている方は、ぜひ手に取ってみてください! 書くスキルも設計スキルも飛躍的に上がる! プログラムを読む技術 GW中は首を痛めて、左をほとんど向けなかった、とんとんぼです。 私は最近、「 書くスキルも設計スキルも飛躍的に上がる! プログラムを読む技術 」という書籍を読んでいました。 bookplus.nikkei.com この本は2024年に発売され、当時も一度手に取ったのですが、最近のAIの普及などを受け、改めて読み返してみることにしました。 多くのプログラミング書は「いかにコードを書くか(例:〇〇の実装方法、XX実践入門など)」に焦点を当てたものがほとんどで、「コードの読み方」に特化した本は稀に思えます。しかし、実際の業務ではコードを書くよりも読む時間のほうが圧倒的に長く、比重も大きいのが現実です。さらに、近年では AI がコードを生成してくれるようになったため、提示されたコードの正誤や意図を正しく理解する力はこれまで以上に重要なスキルになっていると感じています。 この本の優れた点は、「理論」と「実践」が明確に分けられていることです。 前半では、コードを読む際の視点や意識すべきポイントについて理論と少しのサンプルコードから学び、後半では、そこで得た知識を活かして実際にさまざまなコードを読み解いていく構成になっています。 サンプルコードには、Python が採用されているため、読みやすく、実際の仕事でのコードでも実践しやすいのも魅力です。 最後に 最後までお付き合いいただきありがとうございました! それぞれ異なる職種のメンバーによる選書はいかがでしたでしょうか? 記事をまとめていて、自分自身も手を伸ばしてみたくなる書籍がありました。 AIで簡単に情報が手に入る時代ですが、本ならではの説得力や納得感を今回改めて感じました。 もし気になる書籍がありましたらぜひ読んでみてください! herp.careers
Goのtime.Nowとは? 〜synctestを添えて〜 はじめに エブリーでエンジニアをやっております、 赤川 です。食事管理アプリ ヘルシカ の開発を通じてGoを嗜んでいます。 ダイエット・食事管理・体重管理・カロリー計算 - ヘルシカ every, Inc. ヘルスケア/フィットネス 無料 ふと、以下のコードを見て、「Goにおける現在時刻ってなんなんだ…?」となりました。 now := time.Now() OSから取って来ているのは既知とした上で、Goのコードでそれをどのような形で扱っているのか、synctestの仮想時刻を返す挙動がどのように実装されているのかなど、いろいろ気になったのでコードを追っていこうと思います。 本記事で話すこと Goの時刻の扱い方 Goの現在時刻の取得の実装(ある程度高レイヤーの部分のみ) 本記事で話さないこと 他プログラミング言語の現在時刻の取得方法との違い プラットフォーム別実装など低レイヤーの詳細 time.Now の戻り値の作られ方 ウォールクロックとモノトニッククロック まず、OSが提供する時刻ソースには、大きく2種類あります。 種類 内容 特徴 ウォールクロック 現実で扱われている時刻。OSはUNIXエポック(1970-01-01 UTC)からの経過秒数として返すことが多い NTP補正・サマータイム・手動変更で 過去に巻き戻ることがある モノトニッククロック マシン起動などを起点とした、単調増加するカウンタ 必ず単調増加。日付としての意味は持たない これらの扱いについて、 time パッケージの公式ドキュメント に方針が書かれています。 Operating systems provide both a “wall clock,” which is subject to changes for clock synchronization, and a “monotonic clock,” which is not. The general rule is that the wall clock is for telling time and the monotonic clock is for measuring time. Rather than split the API, in this package the Time returned by time.Now contains both a wall clock reading and a monotonic clock reading; later time-telling operations use the wall clock reading, but later time-measuring operations, specifically comparisons and subtractions, use the monotonic clock reading. OSは「ウォールクロック」と「モノトニッククロック」の2つを提供している。ウォールクロックはクロック同期のために変更されうるが、モノトニッククロックは変更されない。一般的なルールとして、ウォールクロックは時刻を知るため(telling time)に、モノトニッククロックは時間を測るため(measuring time)に使う。本パッケージではAPIを分けるのではなく、 time.Now が返す Time にウォールクロックとモノトニッククロックの両方の読み取り値を含めることにしている。以降の「時刻を知る」操作はウォールクロックの値を、「時間を測る」操作(具体的には比較と差分)はモノトニッククロックの値を使う。 time.Timeの構造 「ウォール/モノトニックの両方を1つの Time で扱う」という方針を踏まえて、 src/time/time.go#L140-L161 で定義されている time.Time の構造を見てみましょう。 type Time struct { // wall and ext encode the wall time seconds, wall time nanoseconds, // and optional monotonic clock reading in nanoseconds. // // From high to low bit position, wall encodes a 1-bit flag (hasMonotonic), // a 33-bit seconds field, and a 30-bit wall time nanoseconds field. // The nanoseconds field is in the range [0, 999999999]. // If the hasMonotonic bit is 0, then the 33-bit field must be zero // and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext. // If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit // unsigned wall seconds since Jan 1 year 1885, and ext holds a // signed 64-bit monotonic clock reading, nanoseconds since process start. wall uint64 ext int64 // loc specifies the Location that should be used to // determine the minute, hour, month, day, and year // that correspond to this Time. // The nil location means UTC. // All UTC times are represented with loc==nil, never loc==&utcLoc. loc *Location } 中身は wall 内に持っている hasMonotonic フラグによって2パターンに切り替わります。 hasMonotonic = 1 (ウォール+モノトニック) hasMonotonic = 0 (ウォールのみ) wall フラグ(1) + ウォール秒(33bit, 1885年起点) + ウォールナノ秒(30bit) フラグ(0) + 33bit秒は未使用(0) + ウォールナノ秒(30bit) ext モノトニッククロック値 (プロセス起動からのナノ秒) ウォール秒 (西暦1年起点の符号付き64bit) loc タイムゾーン タイムゾーン ext が hasMonotonic で意味を切り替えるようになっているのは、 wall の33bit秒(1885年起点)だと 1885〜2157年の約272年分 しか表現できないからです。 time.Date(1500, ...) のような範囲外の時刻を扱う hasMonotonic = 0 のケースでは、ウォール秒を wall の33bitから ext (int64, 西暦1年起点) に移してより広い範囲をカバーします。これは Time のサイズを増やさずに「モノトニック付き」と「広い時刻範囲」を両立させるための工夫です。次節の time.Now 実装の中にも、以下のように33bit上限に言及するコメントが出てきます。 // This will be true after March 16, 2157. time.Now 本体の実装 ここまで把握した上で、 src/time/time.go#L1347-L1361 にある time.Now の実装を見ていきます(Go 1.26.3 現在)。 // Now returns the current local time. func Now() Time { sec, nsec, mono := runtimeNow() if mono == 0 { return Time{ uint64 (nsec), sec + unixToInternal, Local} } mono -= startNano sec += unixToInternal - minWall if uint64 (sec)>> 33 != 0 { // Seconds field overflowed the 33 bits available when // storing a monotonic time. This will be true after // March 16, 2157. return Time{ uint64 (nsec), sec + minWall, Local} } return Time{hasMonotonic | uint64 (sec)<<nsecShift | uint64 (nsec), mono, Local} } コードに出てくる定数は同じく src/time/time.go の L163-L169 ( hasMonotonic など)と L535-L568 ( unixToInternal など)、および L1341 ( startNano )に以下のように定義されています。(簡単のため一部省略して記述しています) const ( secondsPerDay = 24 * 60 * 60 // 西暦1年1月1日 〜 UNIXエポック(1970-01-01) の秒数 unixToInternal int64 = ( 1969 * 365 + 1969 / 4 - 1969 / 100 + 1969 / 400 ) * secondsPerDay // 西暦1年1月1日 〜 1885年1月1日 の秒数 wallToInternal int64 = ( 1884 * 365 + 1884 / 4 - 1884 / 100 + 1884 / 400 ) * secondsPerDay ) const ( hasMonotonic = 1 << 63 // wall の最上位bitに立てるフラグ minWall = wallToInternal // wall の33bit秒の起点(= 1885年) nsecShift = 30 // wall に秒を詰めるときのシフト量 ) // プロセス起動時点のモノトニック値(runtime 初期化時にセットされる) var startNano int64 (1969*365 + 1969/4 - 1969/100 + 1969/400) の式は閏年を考慮してその年までの日数を計算しています。これに secondsPerDay を掛けることで「西暦1年1月1日からその年までの 秒数 」になります。 unixToInternal は1970年、 wallToInternal は1885年までのそれにあたります。 役割を整理すると以下のとおりです。 定数 役割 unixToInternal OSが返すUNIX秒を 「西暦1年起点」 に変換するオフセット minWall (= wallToInternal ) 西暦1年起点を 「1885年起点」 にずらすオフセット( wall の33bit秒の基準合わせ) nsecShift wall に秒を詰めるとき左に30bitシフトしてナノ秒の場所を空ける hasMonotonic モノトニックが入っているかのフラグ( wall の最上位bit) startNano プロセス起動時点のモノトニック値。これを引くことで ext を 「プロセス起動からの経過ns」 に正規化する Local loc に入れるデフォルトのタイムゾーン( *Location ) これらを踏まえてもう一度 time.Now を読み直すと、3パターンで Time を組み立てていることがわかります。 func Now() Time { // OSから現在のウォール秒・ナノ秒・モノトニック値を取得 sec, nsec, mono := runtimeNow() // 【パターン1】モノトニッククロックが取れなかった環境 // → ウォールクロックだけを ext に入れて返す(hasMonotonic = 0 のレイアウト) if mono == 0 { // sec(UNIX秒) + unixToInternal で「西暦1年起点の秒」に変換し、ext に詰める return Time{ uint64 (nsec), sec + unixToInternal, Local} } // 以降は mono あり mono -= startNano // モノトニックを「プロセス起動からの経過ns」に正規化 sec += unixToInternal - minWall // sec を「1885年起点の秒」に変換(33bit領域に詰める準備) // 【パターン2】33bitに収まらない(= 2157年3月16日以降) // → モノトニックを諦めて、ウォール秒は ext の方に置く(hasMonotonic = 0 のレイアウト) if uint64 (sec)>> 33 != 0 { // sec はいま1885年起点。minWall を足し戻して「西暦1年起点」に戻してから ext へ return Time{ uint64 (nsec), sec + minWall, Local} } // 【パターン3】通常パス // → hasMonotonic フラグを立て、ウォール・モノトニック両方を wall / ext に詰める // - sec は1885年起点のまま wall の33bit領域へ(<< nsecShift でナノ秒の場所を空けて | で合成) // - mono は正規化済みの値をそのまま ext へ return Time{hasMonotonic | uint64 (sec)<<nsecShift | uint64 (nsec), mono, Local} } runtimeNow() の中身 runtimeNow() は time パッケージ側ではシグネチャだけ書かれており、実体は src/runtime/time.go#L16-L31 にあります。 //go:linkname time_runtimeNow time.runtimeNow func time_runtimeNow() (sec int64 , nsec int32 , mono int64 ) { if bubble := getg().bubble; bubble != nil { sec = bubble.now / ( 1000 * 1000 * 1000 ) nsec = int32 (bubble.now % ( 1000 * 1000 * 1000 )) // Don't return a monotonic time inside a synctest bubble. // If we return a monotonic time based on the fake clock, // arithmetic on times created inside/outside bubbles is confusing. // If we return a monotonic time based on the real monotonic clock, // arithmetic on times created in the same bubble is confusing. // Simplest is to omit the monotonic time within a bubble. return sec, nsec, 0 } return time_now() } 分岐は2つあります。 synctest bubble の分岐 : 実行中のゴルーチンが synctest のバブル内にいる場合、バブルの仮想時刻 bubble.now を ウォール秒・ナノ秒として返し 、モノトニックは 0 を返します。 通常の分岐 : time_now() を呼び出します。これの実体はプラットフォーム別に実装されており、最終的にはどれもOSが提供する時刻取得APIを叩いています。 time_now() の実装は低レイヤーに近い話になるので今回は触れません。 bubble.now は、 src/runtime/synctest.go#L186-L187 の synctestRun で初期化されます。 const synctestBaseTime = 946684800000000000 // midnight UTC 2000-01-01 bubble.now = synctestBaseTime モノトニックを使わない理由については、コメントに書かれています。以下、和訳です。 synctestバブル内ではモノトニック時刻を返さないようにする。 フェイククロックに基づいたモノトニック時刻を返してしまうと、バブル内で作った時刻とバブル外で作った時刻のあいだでの計算結果が紛らわしくなる。 一方で実モノトニッククロックに基づいたモノトニック時刻を返しても、同じバブル内で作った時刻同士の計算が紛らわしくなる。 もっともシンプルな解は、バブル内ではモノトニック時刻を省略することだ。 モノトニッククロックはマシン起動を起点とするものなので、ウォールクロックだけ仮想時刻を進めても両者の値が食い違ってしまいます。一方で synctestBaseTime を起点にしたモノトニック値を別途用意するという選択肢もありますが、その場合もバブル外で取得した Time との差分計算で実時間とバブル内仮想時間が混在してしまいます。これらを避けるために、バブル内ではウォールクロックのみを扱う実装になっている、ということですね。 まとめ time.Now() の経路は以下のようになっていることがわかりました。 time.Now() │ │ ① (sec, nsec, mono) を取得 └── time.runtimeNow() ── linkname ──→ runtime.time_runtimeNow() │ ├── synctest bubble 内 │ sec = bubble.now / 1e9 (バブル仮想時刻) │ nsec = bubble.now % 1e9 │ mono = 0 (バブル内ではmonoを返さない) │ └── 通常経路 runtime.time_now() (プラットフォーム別実装) └─ OSが提供する時刻取得APIを呼ぶ │ │ ② 受け取った値を Time{wall, ext, loc} に組み立てて返す │ ├── mono == 0 → hasMonotonic=0, ext に「西暦1年起点ウォール秒」 ├── 通常 (33bit以内) → hasMonotonic=1, wall=フラグ|33bit秒(1885年起点)|30bitナノ秒, ext=mono(プロセス起動からのns) └── 33bit溢れ (2157年以降) → hasMonotonic=0, ext に「西暦1年起点ウォール秒」 Goが扱う時刻の構造とその設計理由、synctestの分岐実装、そしてウォールとモノトニックを巧みに組み合わせる工夫を知ることができ、とても満足しています。プラットフォームごとの実装は、まずは自分が使っているarm64から見てみたいと思います。 最後までお読みいただきありがとうございました! 参考 Package time - pkg.go.dev Package testing/synctest - pkg.go.dev Go: src/time/time.go Go: src/runtime/time.go Go: src/runtime/synctest.go Go 1.9 Release Notes - Monotonic Clocks Proposal: Monotonic Elapsed Time Measurements in Go

動画

書籍