Goにおけるパッケージの利用可能範囲と共通化の自分ベストプラクティス
Goにおけるパッケージの利用可能範囲と共通化の自分ベストプラクティス
なぜ調べたのか?
Goを使ったプロジェクトで以下のような問題に直面し、調査を行いました。
- ディレクトリ間でのパッケージ利用時のエラー
- 既存コードの意図を汲み取る必要性
- 他者が書いた既存コードを理解する過程で、特定のパッケージがどこまで利用可能なのかを知りたい場面がありました。
これらの背景から、Goにおけるパッケージの適用範囲や構造に関する知識を整理する必要があると感じました。
結論
調査の結果、以下の知見を得ました。
- 機能上のパッケージのスコープ
go.mod以下に存在するすべてのディレクトリにパッケージはアクセス可能。- ただし、同じパッケージ名が複数存在すると名前衝突が発生するため、別名をつけてインポートする必要がある。
- 実務での運用ルール
- 共通機能の整理方法
以下のようなディレクトリ構造を採用すると、コードの再利用性が向上しやすい。
RootDir ├─ common/ │ ├─ utility.go // 共通機能 │ ├─ package common ├─ A/ │ ├─ middleware/ │ │ ├─ xx.go │ │ ├─ package middleware │ ├─ main.go ├─ B/ │ ├─ middleware/ │ │ ├─ xx.go │ │ ├─ package middleware │ ├─ main.go ├─ C/ │ ├─ hoge/ │ │ ├─ main.go ├─ go.mod
パッケージの適応範囲の検証内容
ケース1: go.mod配下でのパッケージ利用
以下のディレクトリ構成では、CディレクトリからAのmiddlewareパッケージの関数を呼び出せます。
RootDir/ ├─ A/ │ ├─ middleware/ │ │ ├─ xx.go (package middleware) │ │ ├─ AMiddlewareFunc ├─ B/ │ ├─ middleware/ │ │ ├─ xx.go (package middleware) │ │ ├─ BMiddlewareFunc ├─ C/ │ ├─ hoge/ │ │ ├─ main.go ├─ go.mod
検証結果:
C/hoge/main.go内でimport "RootDir/A/middleware"とすることで、AMiddlewareFuncを呼び出せました。- すべてのディレクトリが同じ
go.modの管理下にある場合、パッケージの利用に制約は少ない。
ケース2: 各ディレクトリに独自のgo.modが存在する場合
以下のようにディレクトリごとに独自のgo.modを持たせた場合、CディレクトリからAやBのパッケージを利用できません。
RootDir/ ├─ A/ │ ├─ middleware/ │ │ ├─ xx.go (package middleware) │ │ ├─ AMiddlewareFunc │ ├─ go.mod │ ├─ main.go ├─ B/ │ ├─ middleware/ │ │ ├─ xx.go (package middleware) │ │ ├─ BMiddlewareFunc │ ├─ go.mod │ ├─ main.go ├─ C/ │ ├─ hoge/ │ │ ├─ main.go
検証結果:
C/hoge/main.goからA/middlewareやB/middlewareを呼び出せません。- 各ディレクトリが独立した
go.modを持つ場合、外部パッケージとして明示的にインポートする必要があります。
共通化のためのディレクトリ構造の提案
以下の構造を採用することで、共通機能の管理が容易になります。
RootDir/ ├─ common/ │ ├─ utility.go (package common) ├─ A/ │ ├─ middleware/ │ │ ├─ xx.go (package middleware) │ ├─ main.go ├─ B/ │ ├─ middleware/ │ │ ├─ xx.go (package middleware) │ ├─ main.go ├─ C/ │ ├─ hoge/ │ │ ├─ main.go ├─ go.mod
このように、commonディレクトリを作成して共通機能を格納すると、以下のメリットがあります。
- コードの再利用性向上
- 共通処理を一元化できるため、修正が容易。
- 名前衝突の回避
- 各パッケージが独立しているため、衝突の心配がない。
- 保守性の向上
- 共通機能が明確に分離され、開発者全員がアクセスしやすくなる。
まとめ
Goでは、go.mod以下のディレクトリでパッケージを利用できますが、名前衝突の可能性を考慮する必要があります。実務ではディレクトリ単位でパッケージを分離し、共通機能をcommonディレクトリなどに集約することが推奨されます。このような構造を採用することで、コードの保守性や再利用性を高めることができます。
Goプロジェクトを設計する際の参考になれば幸いです!
【golang】変数
概要・ポイント
- 概要
- 変数定義について
- 基本的な変数定義
- 定数
- 変数定義について
- ポイント
- varで変更可能な変数定義が可能
- 型推論機能がある
内容
- 変数定義
基本的な変数の定義は varで作成できる
package main import "fmt" func main() { var name string= "tanaka" fmt.Println("name: ", name) // => name: tanakaと出力される // 型が推論されて stringは不要 var name = "yamada" fmt.Println("name: ", name) // => name: yamadaと出力される var a, b int = 1 ,2 fmt.Println("a: ", a) fmt.Println("b: ", b) // => a: 1と出力される // => b: 2と出力される // varを両略可能 fruit := "apple" fmt.Println("fruit: ", fruit) // =>fruit: appleと出力される }
- 定数
定数はmain関数の外にもかける。他の言語と同様で、変更は不可
package main
import (
"fmt"
"math"
)
const s string = "constant"
func main() {
fmt.Println(s)
const n = 500000000
const d = 3e20 / n
fmt.Println(d)
fmt.Println(int64(d))
fmt.Println(math.Sin(n))
}
感想
型推論の部分がTypescriptな雰囲気を感じました。
:=の省略記法は最初は慣れないですが、書いていると結構便利かも
参考
- 参考サイト
【golang】 hello world
概要・ポイント
- 概要
- goでhello worldを標準出力してみる
- ポイント
- goはパッケージ単位でコードを管理することが可能
- go run xx.goで実行が可能
- go build xx.goでバイナリファイルを作成して実行も可能
内容
- コード go はパッケージ単位(関数単位)で実行が出来る様です。
// main.goファイル // mainの場合は特別らしい。エントリポイントであることを認識させる package main // formatの略で、標準出力関数が利用できます import "fmt" func main() { fmt.Println("hello world") }
- 実行
コンパイルしないでもローカルであれば下記で実行可能
go run main.go
参考
- mac で環境構築
- 参考サイト
Reactでスプレッド演算子を使ったデータ更新についてまとめてみました
Reactでデータを更新するときにスプレッド演算子をよく使うことがあります。今回は、その使い方と理由、具体的な使いどころをまとめてみました。
スプレッド演算子の使い方
まずは基本的な使い方をおさらいします。スプレッド演算子(...)は、オブジェクトや配列のコピーを作るために使います。例えば、以下のように使います:
const originalArray = [1, 2, 3]; const newArray = [...originalArray, 4]; // [1, 2, 3, 4] const originalObject = { a: 1, b: 2 }; const newObject = { ...originalObject, c: 3 }; // { a: 1, b: 2, c: 3 }
なんで使うのか?
スプレッド演算子を使う理由は、主にイミュータブルの原則に従うため です。Reactでは、状態(state)を直接変更しないようにするのが基本です。直接変更すると、Reactが変更を検知できなくて、UIが正しく更新されないことがあるからです。
イミュータブルなデータ構造を使うことで、Reactが状態の変更を正しく検知できるようにします。つまり、元のオブジェクトや配列をそのまま変更せず、新しいコピーを作ることで状態を更新するのです。
どんな時に使うか?
スプレッド演算子を使うタイミングは、主に以下のような場合です:
- 状態を更新するとき: 状態を更新するときは、元の状態をコピーして、新しい値を追加したり変更したりします。
const [state, setState] = useState({ name: 'John', age: 30 });
const updateAge = () => {
setState(prevState => ({
...prevState,
age: prevState.age + 1
}));
};
- 配列を操作するとき: 配列に新しい要素を追加したり、要素を削除したりするときにも使います。
const [items, setItems] = useState([1, 2, 3]);
const addItem = () => {
setItems(prevItems => [...prevItems, 4]);
};
const removeItem = () => {
setItems(prevItems => prevItems.filter(item => item !== 2));
};
まとめ
スプレッド演算子を使うことで、Reactのイミュータブルの原則に従いながら、状態の更新や配列の操作をシンプルに行うことができます。元のデータを直接変更せず、新しいコピーを作ることで、Reactが変更を正しく検知してUIを更新できるようにするのです。
Reactでデータを扱うときは、スプレッド演算子を上手に使って、効率よくイミュータブルなデータ構造を保つようにしましょう。
初心者向け:useStateがなかったらJavaScriptでどうやって実装するか
はじめに
この記事では、ReactのuseStateフックを使わずに、JavaScriptでUIの状態管理を行う方法について説明します。useStateを使用することで状態管理が非常に簡単になりますが、初心者の方にとってその背後にある基本的な概念を理解することも重要です。ここでは、イベント処理を使ってUIを変更する方法と、useStateを使った方法の両方を紹介します。
サンプル

今回はサンプルとして、ボタンをクリックしたら文字が変わる実装を元に、従来のJSのでの実装と、useStateを利用を比較してみて行きたいと思います。
useStateがない場合の実装
まず、useStateを使わずにイベント処理でUIを変更するコードを見てみましょう。ここでは、ボタンをクリックするとテキストが変わるシンプルな例を使います。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>State Management without useState</title> </head> <body> <div id="app"> <p id="text">初期テキスト</p> <button id="changeTextButton">テキストを変更</button> </div> <script> document.addEventListener('DOMContentLoaded', () => { const textElement = document.getElementById('text'); const buttonElement = document.getElementById('changeTextButton'); buttonElement.addEventListener('click', () => { textElement.textContent = '変更後のテキスト'; }); }); </script> </body> </html>
この例では、ボタンがクリックされると、JavaScriptのイベントリスナーがテキストを変更します。useStateを使わずに、DOM操作だけでUIを更新しています。
useStateを利用したコード
次に、同じ機能をReactのuseStateフックを使って実装してみましょう。
import React, { useState } from 'react'; function App() { const [text, setText] = useState('初期テキスト'); const handleChangeText = () => { setText('変更後のテキスト'); }; return ( <div> <p>{text}</p> <button onClick={handleChangeText}>テキストを変更</button> </div> ); } export default App;
こちらのコードでは、useStateを使って状態を管理しています。useStateは現在の状態とそれを更新する関数を返し、状態が変更されると自動的にUIが再レンダリングされます。
まとめ
useStateがない場合でも、イベント処理を使ってUIの状態を変更することは可能です。しかし、ReactのuseStateフックを使用することで、状態管理がよりシンプルで直感的になります。初心者の方は、まず基本的なJavaScriptによるDOM操作を理解し、その後でuseStateを使った状態管理に進むと、Reactの強力な機能をより深く理解できるでしょう。
単一方向バインディングと双方向バインディングの違いとメリット・デメリットを理解する
背景
最近、UIライブラリのOSS開発者の方と話していて、「双方向バインディングは避けたほうが良い」と聞いて、ちょっと興味が湧きました。そこで、単一方向バインディングと双方向バインディングの違いやメリット・デメリットについてまとめてみました。
単一方向バインディングと双方向バインディングのメリット・デメリット
単一方向バインディング
メリット:
- シンプルでわかりやすい: データの流れが一方向なので、コードの追跡が簡単。
- デバッグが容易: データの変更が予測しやすく、バグが発生した際に原因を見つけやすい。
- パフォーマンス向上: データの変更が少ないため、無駄な再レンダリングが減る。
デメリット:
- コードが冗長になる: 状態管理が複雑な場合、コード量が増える。
- 双方向の連携が面倒: 双方向のデータ連携が必要な場合、手動での更新が必要。
双方向バインディング
メリット:
- 直感的: フォーム入力など、ユーザーのアクションに対して即時に反応できる。
- コードが短くなる: 自動でデータが同期されるため、コード量が減る。
デメリット:
ReactとAngularでのサンプルコードを見比べてみます
Reactでの単一方向バインディング
import React, { useState } from 'react'; const SingleWayBinding = () => { const [value, setValue] = useState(''); const handleChange = (event) => { setValue(event.target.value); }; return ( <div> <input type="text" value={value} onChange={handleChange} /> <p>{value}</p> </div> ); }; export default SingleWayBinding;
Reactの場合、valueが変更されると、再度レンダリングして新しい値を用いて、UIを変更してますよね。 これはデータ => UIの一方方向が実現できていると思います。 とても分かりやすいですね。
Angularでの双方向バインディング
// app.module.ts import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; @NgModule({ declarations: [AppComponent], imports: [BrowserModule, FormsModule], providers: [], bootstrap: [AppComponent], }) export class AppModule {} // app.component.ts import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent { value: string = ''; } // app.component.html <div> <input [(ngModel)]="value" type="text" /> <p>{{ value }}</p> </div>
一方で、Angularの場合は、value(データ)を用いてUIを表示して、inputからvalueが更新されると、ReactのようにonChangeの関数などのでデータを更新せずともUIからデータを更新できてしまう仕組みになっています。 これは便利な部分もあると思うのですが、データが絡みあっているのでデバックがしずらく可能性がありそうですね。
まとめ
個人的には、Reactの単一方向バインディングの方がデータフローがシンプルでメンテナンスしやすいと感じました。特に大規模なアプリケーションでは、単一方向バインディングの方がバグを避けやすいかもしれません。
単一方向バインディングと双方向バインディングにはそれぞれメリットとデメリットがあります。ユースケースによってどちらが適しているかは異なるため、絶対に単一方向バインディングが良いというわけではありません。自分のプロジェクトに合った方法を選ぶことが大切です。
React Reduceの使い方をマスターしよう!
背景
JavaScriptのreduceというメソッドは、存在は知っていても実際にどのような場面で使うのかがわかりにくいことがあります。私も以前はその一人でしたが、最近購入履歴のデータを加工してチャートに反映する機会があり、その時にreduceの便利さを実感しました。具体的には、Rechartsを使ってグラフを描くために、購入履歴データを集計する必要がありました。
jsのreduceの挙動
まず、reduceの基本的な挙動について説明いたします。reduceは配列を一つの値にまとめるための関数ですが、初期値(initialValue)が数字の場合とオブジェクトの場合で少し使い方が変わります。
初期値が数字の場合
例えば、配列内の数字の合計を求める場合は以下のようにします:
const numbers = [1, 2, 3, 4]; const sum = numbers.reduce((acc, current) => acc + current, 0); console.log(sum); // 10
ここで、accは累積値で、currentが現在の要素です。initialValueとして0を渡しているので、最初のaccは0からスタートします。
初期値がオブジェクトの場合
次に、初期値がオブジェクトの場合を見てみましょう。例えば、購入履歴をカテゴリごとに集計したい場合は以下のようになります:
const purchaseHistories = [ { id: 1, amount: 100, category: { name: '外食' } }, { id: 2, amount: 50, category: { name: '交際費' } }, { id: 3, amount: 100, category: { name: 'Netflix' } }, { id: 4, amount: 150, category: { name: '外食' } }, ]; const categoryTotals = purchaseHistories.reduce((acc, history) => { const category = history.category.name; if (!acc[category]) { acc[category] = 0; } acc[category] += history.amount; return acc; }, {}); console.log(categoryTotals); // { '外食': 250, '交際費': 50, 'Netflix': 100 }
ここで、初期値として空のオブジェクト{}を渡しているため、各カテゴリの合計金額を計算することができます。
実際に利用した場面
次に、実際にどのように使ったかを見てみましょう。
上記のreduceを使って集計したデータを元に、Rechartsで画像の様なパイチャートを描きます。
以下のコードをご覧ください:
'use client' import { Cell, Pie, PieChart, ResponsiveContainer } from 'recharts' const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042'] const RADIAN = Math.PI / 180 interface CustomizedLabelProps { cx: number cy: number midAngle: number innerRadius: number outerRadius: number percent: number index: number name: string value: number } const renderCustomizedLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, percent, index, name, value, }: CustomizedLabelProps) => { const radius = innerRadius + (outerRadius - innerRadius) * 0.5 const x = cx + radius * Math.cos(-midAngle * RADIAN) const y = cy + radius * Math.sin(-midAngle * RADIAN) return ( <text x={x} y={y} fill="white" textAnchor={x > cx ? 'start' : 'end'} dominantBaseline="central" fontSize="13px" > {`${name} ${(percent * 100).toFixed(0)}%`} </text> ) } const purchaseHistories = [ {id: 1, amount: 100 ,category: { name: '外食'}}, {id: 2, amount: 50 ,category: { name: '交際費'}}, {id: 3, amount: 100 ,category: { name: 'Netflix'}}, {id: 4, amount: 150 ,category: { name: '外食'}}, ] const RechartPipeChart = () => { const groupByCategoryPurchaseHistories = () => { const categoryTotals = purchaseHistories.reduce( (acc: Record<string, number>, history) => { const category = history.category.name if (!acc[category]) { acc[category] = 0 } acc[category] += history.amount return acc }, {}, ) return Object.keys(categoryTotals).map((category) => ({ name: category, value: categoryTotals[category], })) } const groupedData = groupByCategoryPurchaseHistories() return ( <> <ResponsiveContainer width="100%" height={300}> <PieChart width={500} height={500}> <Pie data={groupedData} cx="50%" cy="50%" labelLine={false} label={({ cx, cy, midAngle, innerRadius, outerRadius, percent, index, }) => renderCustomizedLabel({ cx, cy, midAngle, innerRadius, outerRadius, percent, index, name: groupedData[index].name, value: groupedData[index].value, }) } outerRadius={150} fill="#8884d8" dataKey="value" > {groupedData.map((entry, index) => ( <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} /> ))} </Pie> </PieChart> </ResponsiveContainer> </> ) } export default RechartPipeChart
ここでは、purchaseHistoriesをreduceを使ってカテゴリごとに集計し、その結果をmapでRechartsが扱いやすい形に変換しています。これにより、購入履歴のデータを元にしたパイチャートを表示することができます。
まとめ
reduceは一見難しそうに見えますが、使い方を覚えるとデータを集計したり変換したりするのに非常に便利です。特に初期値をうまく設定することで、様々なデータの操作が可能になります。今回の例では、購入履歴のデータをカテゴリごとに集計してチャートに反映する方法をご紹介しましたが、他にも多くの場面で応用できますので、ぜひ試してみてください。