Go言語の魅力:人間とAIに優しいコード

皆さん、こんにちは!今回は、私が感じているGo言語の魅力についてお話ししたいと思います。 Go言語は、非常にシンプルで基本的な要素で構成されているプログラミング言語です。しかし、その最大の特長の一つは、**コードの書き方について、ある程度の規律や統一性が強く推奨されている**点にあると感じています。 具体的には、公式のフォーマッターやリンターなど、開発者ができるだけ「あるべき書き方」に従うことを促す仕組みが充実しています。このような特性がもたらす大きな利点は、生成されるコードの品質と可読性です。 AIがコード生成を担う機会が増えている現代において、Go言語のように規律の取れたコードスタイルが確立されていることは非常に重要です。たとえAIによって書かれたコードであっても、その構造や表現が統一されているため、**人間が読んで理解しやすく、また自身で修正や拡張を行いやすい**というメリットが生まれます。 AI任せにするだけでなく、人間が協調して開発を進める上で、Go言語の持つ「人間にもAIにも優しい」という特性は、今後ますます価値を高めていくのではないでしょうか。

Goでランダムなuuidを生成する

Goでランダムなuuidを生成する

Goでアプリ開発をしているときにuuidを使用しかったので調べてみた

使用するライブラリ:https://github.com/google/uuid

やること

uuidをインストールする

go get github.com/google/uuid

実装してみる 参考:https://pkg.go.dev/github.com/google/uuid#NewString

package main

import (
    "fmt"

    "github.com/google/uuid"
)

func main() {
    id := uuid.New()
    fmt.Println(id)       // 出力例: f47ac10b-58cc-4372-a567-0e02b2c3d479
    fmt.Printf("%T\n", id) // 出力: uuid.UUID

    idStr := id.String()
    fmt.Println(idStr)    // 出力例: f47ac10b-58cc-4372-a567-0e02b2c3d479
    fmt.Printf("%T\n", idStr) // 出力: string
}
  • uuid.New() を使うと ランダムなUUID(バージョン4) が生成されます。
  • uuid.UUID 型で返されるため、必要に応じてString() メソッドで文字列に変換できます。
  • 文字列に変換すると、データベースへの保存やAPIレスポンスへの利用などがしやすくなります。

3年目の目標

はじめに

4月から社会人として3年目になった

保守業務がメインだったので実装する能力が低く課題に感じている エンジニアとして生きていきたい気持ちが大きくなったので異動届けを出して、開発能力の求められるチームに異動することになった

そのためエンジニアとして能力を身につけるための目標を設定しようと思う

エンジニア3年目として意識すること

  • 多めに見積もって目標を立てるので達成できない部分はご愛嬌でお願いしたい
技術の幅と深さをどう広げるか
  • チーム内でトップレベルでGoについて詳しく高いレベルを求められるエンジニアになりたい
  • 自分の領域をバックエンド(Go)とインフラ(AWS,Iac)として極めていく
  • 仕事を勝手に生み出して、社内においての自分の領域や担当範囲を広げていく

目標達成のためにやること

やること

  • Goの標準パッケージを理解してコードリーディングを進める
  • KubernetesやTerraformなどのGoで書かれているOSSのコードリーディングする
  • 技術書を読んでインプットする
    • Go(5,6冊)
    • React(2冊)
    • AWS(3冊)
    • Terraform(2,3冊)
    • Kubernetes(2冊)
    • Docker(1冊)
    • ネットワーク(2冊)
    • OAuth(3冊)
    • DB(3冊)
    • Linux(2冊)

達成するための習慣

  • 月に2~4本テックブログを書くようにする
  • 月に2~4冊技術書を読む
  • 1日3ページ以上、技術書を読む
  • 早めに出勤して30分以上学習する
  • 技術の勉強は全部作ってみる

最後に

大抵はできないのではなくて、やるべきことを知っているけどやらないという方が近いものです

また開発に異動するという自分にとっても大きな一年となるため、チャレンジしていくことを増やしていきたい

React Nativeを書く際に実機で動作確認する(Docker)

はじめに

なんかモバイルアプリを触ってみようと思って、React Nativeで色々書こうとしていました。 Windowsで開発していたので、実機確認がXcodeなどでできず、Expoを使うことと、Dockerで立ち上げた時に実機で確認ができるようにしてみました

Expoとは?

説明しようとしたけど、綺麗にまとまっている記事があったので添付いたします。

https://zenn.dev/kamo_tomoki/books/0158a7770edeea/viewer/f58b65

https://docs.expo.dev/

Dockerファイルで立ち上がるようにする

Dockerで立ち上げができるとExpoがホットリロードで確認することができます。 そのため、動作確認などが簡単という利点があります

事前準備

  • Expoにログインしてアカウントを作成する
  • 端末側にもアプリをインストールしておく

Dockerをかく

  • Dockerfile
# ベースイメージとしてnode:18を使用
FROM node:18

# 作業ディレクトリを/appに設定
WORKDIR /app

# 現在のディレクトリの内容をコンテナにコピー
COPY package*.json ./

# npmをインストール
RUN npm install

# 現在のディレクトリの内容をコンテナにコピー
COPY . .

# Expoアプリをトンネルモードで起動
CMD ["npm", "start"]
  • docker-compose.yml
services:
  myapp:
    container_name: myapp-react-native
    tty: true
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - .:/app
    env_file: .env
    ports:
      - 8081:8081
  • .env     - PCと同じIPを端末で指定しないといけません     - そのため、ifconfigなどでipを確認して、envに記載します     - .envの内容はdocker-compose.ymlで指定します
REACT_NATIVE_PACKAGER_HOSTNAME=192.168.x.x

これでdockerのイメージをビルドして、立ち上げると以下のようにQRが出てくるので、端末で読み込みます

つまづいた部分

QRコードは出てきましたが、端末で読み込むと起動できませんでした。

PCと端末のWi-Fiが同じでないといけないので、確認したがうまくいかず

色々調べるとちゃんと設定しているけど、IPが接続できていないよと判定されてしまうことがあるみたいです。 対策として--tunnelを実行する必要があります

https://docs.expo.dev/more/expo-cli/#tunneling

package.jsonに書かれている内容に--tunnelを追加します

 -   "start": "expo start",
 +   "start": "expo start --tunnel",

これでWeb側も端末側も同じ内容になりました! おわり!

PCの画面

iOSの画面

Goの雛形をBlueprintで作成してみる

はじめに

Railsをやっているとrails new app_nameで簡単に雛形を作成できてしまいます。

Goでも同じようなことができないかなと思っていたので、探してみるとgo-blueprintがあったので作成してみることにしました

実行してみる

  • まずgo-blueprintをインストールする
$ go install github.com/melkeydev/go-blueprint@latest
  • createコマンド
$ go-blueprint create

createコマンド後に以下のような画像が表示されます。 プロジェクト名を求められるのでsample_appを指定します 今回はGinを選択しました clip-20250212181549.png Ginを選択した後に、databaseの選択を要求されます Postgresを選択する

ここまでで基本的なファイルなどが作成されます Makefileも自動的に作成されるので、make runで起動することができます

$ make run
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /                         --> sample_app/internal/server.(*Server).HelloWorldHandler-fm (4 handlers)
[GIN-debug] GET    /health                   --> sample_app/internal/server.(*Server).healthHandler-fm (4 handlers)

おまけ:具体的な実装

  • sample_app/cmd/api/main.go
package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os/signal"
    "syscall"
    "time"

    "sample_app/internal/server"
)

func gracefulShutdown(apiServer *http.Server, done chan bool) {
    // Create context that listens for the interrupt signal from the OS.
    ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
    defer stop()

    // Listen for the interrupt signal.
    <-ctx.Done()

    log.Println("shutting down gracefully, press Ctrl+C again to force")

    // The context is used to inform the server it has 5 seconds to finish
    // the request it is currently handling
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    if err := apiServer.Shutdown(ctx); err != nil {
        log.Printf("Server forced to shutdown with error: %v", err)
    }

    log.Println("Server exiting")

    // Notify the main goroutine that the shutdown is complete
    done <- true
}

func main() {

    server := server.NewServer()

    // Create a done channel to signal when the shutdown is complete
    done := make(chan bool, 1)

    // Run graceful shutdown in a separate goroutine
    go gracefulShutdown(server, done)

    err := server.ListenAndServe()
    if err != nil && err != http.ErrServerClosed {
        panic(fmt.Sprintf("http server error: %s", err))
    }

    // Wait for the graceful shutdown to complete
    <-done
    log.Println("Graceful shutdown complete.")
}
  • sample_app/internal/database/database.go
package database

import (
    "context"
    "database/sql"
    "fmt"
    "log"
    "os"
    "strconv"
    "time"

    _ "github.com/jackc/pgx/v5/stdlib"
    _ "github.com/joho/godotenv/autoload"
)

// Service represents a service that interacts with a database.
type Service interface {
    // Health returns a map of health status information.
    // The keys and values in the map are service-specific.
    Health() map[string]string

    // Close terminates the database connection.
    // It returns an error if the connection cannot be closed.
    Close() error
}

type service struct {
    db *sql.DB
}

var (
    database   = os.Getenv("BLUEPRINT_DB_DATABASE")
    password   = os.Getenv("BLUEPRINT_DB_PASSWORD")
    username   = os.Getenv("BLUEPRINT_DB_USERNAME")
    port       = os.Getenv("BLUEPRINT_DB_PORT")
    host       = os.Getenv("BLUEPRINT_DB_HOST")
    schema     = os.Getenv("BLUEPRINT_DB_SCHEMA")
    dbInstance *service
)

func New() Service {
    // Reuse Connection
    if dbInstance != nil {
        return dbInstance
    }
    connStr := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable&search_path=%s", username, password, host, port, database, schema)
    db, err := sql.Open("pgx", connStr)
    if err != nil {
        log.Fatal(err)
    }
    dbInstance = &service{
        db: db,
    }
    return dbInstance
}

// Health checks the health of the database connection by pinging the database.
// It returns a map with keys indicating various health statistics.
func (s *service) Health() map[string]string {
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel()

    stats := make(map[string]string)

    // Ping the database
    err := s.db.PingContext(ctx)
    if err != nil {
        stats["status"] = "down"
        stats["error"] = fmt.Sprintf("db down: %v", err)
        log.Fatalf("db down: %v", err) // Log the error and terminate the program
        return stats
    }

    // Database is up, add more statistics
    stats["status"] = "up"
    stats["message"] = "It's healthy"

    // Get database stats (like open connections, in use, idle, etc.)
    dbStats := s.db.Stats()
    stats["open_connections"] = strconv.Itoa(dbStats.OpenConnections)
    stats["in_use"] = strconv.Itoa(dbStats.InUse)
    stats["idle"] = strconv.Itoa(dbStats.Idle)
    stats["wait_count"] = strconv.FormatInt(dbStats.WaitCount, 10)
    stats["wait_duration"] = dbStats.WaitDuration.String()
    stats["max_idle_closed"] = strconv.FormatInt(dbStats.MaxIdleClosed, 10)
    stats["max_lifetime_closed"] = strconv.FormatInt(dbStats.MaxLifetimeClosed, 10)

    // Evaluate stats to provide a health message
    if dbStats.OpenConnections > 40 { // Assuming 50 is the max for this example
        stats["message"] = "The database is experiencing heavy load."
    }

    if dbStats.WaitCount > 1000 {
        stats["message"] = "The database has a high number of wait events, indicating potential bottlenecks."
    }

    if dbStats.MaxIdleClosed > int64(dbStats.OpenConnections)/2 {
        stats["message"] = "Many idle connections are being closed, consider revising the connection pool settings."
    }

    if dbStats.MaxLifetimeClosed > int64(dbStats.OpenConnections)/2 {
        stats["message"] = "Many connections are being closed due to max lifetime, consider increasing max lifetime or revising the connection usage pattern."
    }

    return stats
}

// Close closes the database connection.
// It logs a message indicating the disconnection from the specific database.
// If the connection is successfully closed, it returns nil.
// If an error occurs while closing the connection, it returns the error.
func (s *service) Close() error {
    log.Printf("Disconnected from database: %s", database)
    return s.db.Close()
}
  • sample_app/internal/server/routes.go
package server

import (
    "net/http"

    "github.com/gin-contrib/cors"
    "github.com/gin-gonic/gin"
)

func (s *Server) RegisterRoutes() http.Handler {
    r := gin.Default()

    r.Use(cors.New(cors.Config{
        AllowOrigins:     []string{"http://localhost:5173"}, // Add your frontend URL
        AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"},
        AllowHeaders:     []string{"Accept", "Authorization", "Content-Type"},
        AllowCredentials: true, // Enable cookies/auth
    }))

    r.GET("/", s.HelloWorldHandler)

    r.GET("/health", s.healthHandler)

    return r
}

func (s *Server) HelloWorldHandler(c *gin.Context) {
    resp := make(map[string]string)
    resp["message"] = "Hello World"

    c.JSON(http.StatusOK, resp)
}

func (s *Server) healthHandler(c *gin.Context) {
    c.JSON(http.StatusOK, s.db.Health())
}
  • docker-compose.yml
services:
  psql_bp:
    image: postgres:latest
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${BLUEPRINT_DB_DATABASE}
      POSTGRES_USER: ${BLUEPRINT_DB_USERNAME}
      POSTGRES_PASSWORD: ${BLUEPRINT_DB_PASSWORD}
    ports:
      - "${BLUEPRINT_DB_PORT}:5432"
    volumes:
      - psql_volume_bp:/var/lib/postgresql/data

volumes:
  psql_volume_bp:

これでdockerを立ち上げれば完成です

Goでファイル操作のCLIツールを作成する

はじめに

別にツールじゃなくていいけど、なんかファイル操作とか触ってみないと覚えないし、遠回りしてGoでCLIツール作成してみます

想定読者

何となくGo触ったことある人 Goのファイル操作何となくわかるけど、そんなに触ってない人 GoでCLIツールの実装をしようと思っている人

概要

  • 課題
    • yyyymmdd.mdでタスクを管理しているが、その日に終わらなかったタスクを毎回次の日のファイルにコピペしなければならないのが面倒
  • 最終成果物

    • - [ ]になっている行だけ取得して次の日のmdに追加
    • サブコマンドでタスクを追加
  • 大まかな流れ 1: ファイルの中身を取得してみる 2: - [ ]だけ抽出して翌日のmdに追加する 3: サブコマンドでタスクの追加とタグ付けをする

ファイルの中身を取得する

package main

import (
    "fmt"
    "log"
    "os"
)

func main() {
    // ファイルの中身を一括で読み込む
    content, err := os.ReadFile("20250121.md")
    if err != nil {
        log.Fatalf("ファイルを読み取れませんでした: %v", err)
    }

    // ファイル内容を表示
    fmt.Println("ファイル内容:")
    fmt.Println(string(content))
}
$ go run main.go 
ファイル内容:
- [ ] hogehoge
- [ ] hogehoge

ファイルの中身を別のファイルに書き出す

  • 今日のmdファイルから翌日にmdファイルを出力する
  • ファイル操作によってファイルの読み込み、書き込みなどをする
  • 完了済みのタスクが表示されないようにする
package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
    "strings"
    "time"
)

func main() {
    // 現在自国を取得
    now := time.Now()
    //今日の日付を取得するフォーマット
    nowStr := now.Format("20060102")

    // ファイルの中身を読み込む
    content, err := os.ReadFile(nowStr + ".md")
    if err != nil {
        log.Fatalf("ファイルを読み取れませんでした: %v", err)
    }

    // nowStrから1日追加
    tomorrow := now.AddDate(0, 0, 1)
    tomorrowStr := tomorrow.Format("20060102")
    tomorrow_md := fmt.Sprintf("%s.md", tomorrowStr)

    //ファイルが存在する場合は、作成不要にしておく
    if _, err := os.Stat(tomorrow_md); err == nil {
        fmt.Println("すでに作成されているので作成しません")
    } else {
        // ファイルが存在しない場合、新規作成する
        file, err := os.Create(tomorrow_md)
        if err != nil {
            log.Fatalf("エラーが発生しました: %v", err)
        }
        defer file.Close()
        fmt.Println("新しいファイルを作成しました")
    }

    // 未完了タスクを抽出
    var tasks []string
    scanner := bufio.NewScanner(strings.NewReader(string(content)))
    for scanner.Scan() {
        line := scanner.Text()
        // 未完了タスク行(`- [ ]` を含む)を抽出
        if strings.Contains(line, "- [ ]") {
            // 未完了のタスクを slice に追加(インデントを保持)
            tasks = append(tasks, line)
        }
    }

    output := strings.Join(tasks, "\n")

    // 出力の例(ファイルに書き込む場合)
    err = os.WriteFile(tomorrow_md, []byte(output), 0644)
    if err != nil {
        log.Fatalf("エラーが発生しました: %v", err)
    }

    fmt.Println(tasks)

    if err := scanner.Err(); err != nil {
        log.Fatalf("スキャン中にエラー:%v", err)
    }
    // 未完了タスクがない場合の処理
    if len(tasks) == 0 {
        fmt.Println("未完了タスクはありません。")
        return
    }

    // 未完了のタスクだけを書き込む
    file, err := os.OpenFile(tomorrow_md, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
    if err != nil {
        log.Fatalf("ファイルを開く中にエラー: %v", err)
    }
    defer file.Close()

    fmt.Println("未完了のタスクを書き込みました")

    fmt.Println("今日の内容を更新しました!")
}
  • 入力ファイルの例
    • 完了済みのタスクを用意します
    • インデントも下げて親タスク、子タスクの関係にします
- [ ] xxx
    - [x] xxxx
- [x] xxx
- [ ] yyyy
  • 出力ファイルの例
    • ちゃんと完了済みのタスクのみ出力されています
- [ ] xxx
- [ ] yyyy

サブコマンドの実装

  • サブコマンドを実装するには、フォルダ構造は大きく変更されます。
  • 実装したいことは、大きく変更ないのでサブコマンド使用するならよしなにやってください

初期設定

  • 以下のコマンドでcobraでCLIを実行するために準備する
$ go install github.com/spf13/cobra-cli@latest

$ cobra-cli init --license MIT --viper=false

$  go run main.go
A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.
  • main.goの中身
package main

import "todo_support/cmd"

func main() {
    cmd.Execute()
}
  • generate.goの中身
package cmd

import (
    "bufio"
    "fmt"
    "log"
    "os"
    "path/filepath"
    "strings"
    "time"

    "github.com/spf13/cobra"
)

const tasksDir = "tasks" // タスクファイルを保存するディレクトリ

// generateCmd represents the generate command
var generateCmd = &cobra.Command{
    Use:   "generate",
    Short: "翌日のTODOファイルを生成します",
    Long:  "今日のTODOファイルから未完了のタスクを抽出し、翌日のTODOファイルを新規作成します。",
    Run: func(cmd *cobra.Command, args []string) {
        // 現在自国を取得
        now := time.Now()
        // 今日の日付を取得するフォーマット
        nowStr := now.Format("20060102")

        // ファイルの中身を読み込む
        content, err := os.ReadFile(filepath.Join(tasksDir, nowStr+".md"))
        if err != nil {
            log.Fatalf("ファイルを読み取れませんでした: %v", err)
        }

        // nowStrから1日追加
        tomorrow := now.AddDate(0, 0, 1)
        tomorrowStr := tomorrow.Format("20060102")
        tomorrow_md := filepath.Join(tasksDir, fmt.Sprintf("%s.md", tomorrowStr))

        // ファイルが存在する場合は、作成不要にしておく
        if _, err := os.Stat(tomorrow_md); err == nil {
            fmt.Println("すでに作成されているので作成しません")
        } else {
            // ファイルが存在しない場合、新規作成する
            file, err := os.Create(tomorrow_md)
            if err != nil {
                log.Fatalf("エラーが発生しました: %v", err)
            }
            defer file.Close()
            fmt.Println("新しいファイルを作成しました")
        }

        // 未完了タスクを抽出
        var tasks []string
        scanner := bufio.NewScanner(strings.NewReader(string(content)))
        for scanner.Scan() {
            line := scanner.Text()
            // 未完了タスク行(`- [ ]` を含む)を抽出
            if strings.Contains(line, "- [ ]") {
                // 未完了のタスクを slice に追加(インデントを保持)
                tasks = append(tasks, line)
            }
        }

        output := strings.Join(tasks, "\n")

        // 出力の例(ファイルに書き込む場合)
        err = os.WriteFile(tomorrow_md, []byte(output), 0644)
        if err != nil {
            log.Fatalf("エラーが発生しました: %v", err)
        }

        fmt.Println(tasks)

        if err := scanner.Err(); err != nil {
            log.Fatalf("スキャン中にエラー:%v", err)
        }
        // 未完了タスクがない場合の処理
        if len(tasks) == 0 {
            fmt.Println("未完了タスクはありません。")
            return
        }

        // 未完了のタスクだけを書き込む
        file, err := os.OpenFile(tomorrow_md, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
        if err != nil {
            log.Fatalf("ファイルを開く中にエラー: %v", err)
        }
        defer file.Close()

        fmt.Println("未完了のタスクを書き込みました")

        fmt.Println("今日の内容を更新しました!")
    },
}

func init() {
    rootCmd.AddCommand(generateCmd)
}
  • add.goの中身
    • add.goでは、タスクの内容を追加するためのコマンドを実行します
    • --tommorowのサブコマンドを実装して明日のタスクに追加することができます
    • --tagのサブコマンドでタスクの最初にタグを追加することができます
package cmd

import (
    "fmt"
    "os"
    "time"

    "github.com/spf13/cobra"
)

// addCmd represents the add command
var addCmd = &cobra.Command{
    Use:   "add [タスク内容]",
    Short: "タスクを追加します",
    Long: `このコマンドは今日または翌日のタスクに新しいタスクを追加します。
使用例: 
add '新しいタスク内容' --tag [タグの名前]`,
    Run: func(cmd *cobra.Command, args []string) {
        if len(args) < 1 {
            fmt.Println("addの引数にタスク内容を指定してください")
            return
        }

        // タスク内容を取得
        task := args[0]

        // フラグの値を取得
        tomorrow, _ := cmd.Flags().GetBool("tomorrow")
        tag, _ := cmd.Flags().GetString("tag")

        // 今日または翌日のタスクファイルを決定
        date := time.Now()
        if tomorrow {
            date = date.AddDate(0, 0, 1) // 翌日
        }
        filePath := fmt.Sprintf("tasks/%s.md", date.Format("20060102"))

        // タスク行を作成
        taskLine := ""
        if tag != "" {
            taskLine = fmt.Sprintf("\n- [ ] [%s] %s", tag, task)
        } else {
            taskLine = fmt.Sprintf("\n- [ ] %s", task)
        }

        // タスクファイルに書き込み
        f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
        if err != nil {
            fmt.Println("ファイルを開けませんでした:", err)
            return
        }
        defer f.Close()

        if _, err := f.WriteString(taskLine); err != nil {
            fmt.Println("タスクを追加できませんでした:", err)
        } else {
            fmt.Printf("タスク '%s' を %s に追加しました\n", taskLine, filePath)
        }
    },
}

func init() {
    // フラグの登録を最初に実行
    addCmd.Flags().BoolP("tomorrow", "t", false, "Add task to tomorrow's TODO file")
    addCmd.Flags().StringP("tag", "g", "", "タスクにタグを付与します")

    // ルートコマンドに追加
    rootCmd.AddCommand(addCmd)
}
$ go build -o todo_cli

# addだけだと今日の日付のファイルにタスクが追加される
$ ./todo_cli add "今日のタスク"

# -t(--tomorrow)をサブコマンドとして実行すると明日の日付のファイルにタスクが追加される
$ ./todo_cli add -t "明日のタスク"

# -a(--tag)をサブコマンドとして実行すると追加するタスクにタグが追加される
$ ./todo_cli add "タグの追加" --tag urgent
# - [ ] [urget] タグの追加 

まとめ

ファイル操作覚える必要ないけど、使い機会なかったのでよかった tagの追加などは優先度などを判断するのに使用できそうなので、優先度高いタスクをパッと追加できるのはいいことだなという気がした

Goの構造体で継承っぽいことをする

はじめに

Goで継承は使えないのでどうやるかというと構造体をいい感じで利用して実装します。

使えないってよりは、継承の概念がないと思います。

この記事で書くことが⭐️の内容です。

⭐️1. 構造体⭐️ 1-1. 構造体の書き方 1-2.メソッドと構造体 ⭐️2. 構造体の埋め込み⭐️ 2-1. 構造体を増やし、親子関係を作る 3. インターフェース 4. 構造体とインターフェース

やってみよう!

1,構造体とは

構造体はtypeとstructを使用して定義します。 構造体をどうやってかくかとメソッドについても簡単に触れます

  1. type 構造体名 structで構造体を作成できる
  2. main関数の中でインスタンスを作成する
  3. フィールドに値を代入して使用する
package main

import "fmt"

// 名前のフィールドを持つAnimalという構造体を作成
type Animal struct {
    Name string //名前フィールド
}

func main(){
    // インスタンスを作成する
    var dog Animal
    dog.Name = "ポチ"
    fmt.Println(dog.Name)
}

結果

ポチ

上記の実装に対してSpeakeメソッドを作成します

  1. func (レシーバー名 レシーバー型) メソッド名(引数リスト) 戻り値リストでメソッドを作成する
  2. main関数の中でSpeakメソッドを呼び出す
package main

import "fmt"

// 名前のフィールドを持つAnimalという構造体を作成
type Animal struct {
    Name string //名前フィールド
}

func (a Animal) Speak() {
    fmt.Printf("%s のSpeakメソッドの内容。\n", a.Name)
}
func main(){
    // インスタンスを作成する
    var dog Animal
    dog.Name = "ポチ"
    fmt.Println(dog.Name)
    dog.Speak()
}

結果

ポチ
ポチ のSpeakメソッドの内容

2. 構造体の埋め込み

次にこれらの構造体を複数使用することで親子関係を作ります。

以下の実装では先ほどの実装におけるAnimalを親とするDogとCatの構造体を作成します。 Dogでは、名前・鳴き声、犬種を表示します。 Catでは、名前・鳴き声・好きな魚を表示します。

この時に、名前と鳴き声は共通しているのでAnimalがフィールドとして持ちます。 Dogは、犬種。Catは好きな魚を表示したいので、それぞれの構造体にフィールドを持ちます。

  1. Animalを親としたDogとCatの構造体を作成します。
  2. Animalの持つSpeakメソッドを作成します。この時、DogとVatはこのSpeakメソッドを使用できます。
  3. main関数でインスタンスを作成します。この時、Animalのインスタンスが明示的に立ち上げていませんが、埋め込みをすることで利用できます
  4. そんで1~3で実施した内容をfmtでぺって実行させます
package main

import "fmt"

// Animalの構造体を作成
type Animal struct {
    Name  string
    Voice string
}

// Animalを親にもつDogの構造体を作成
type Dog struct {
    Animal // これでAnimalの構造体を埋め込む
    Breed  string
}

// Animalを親にもつCatの構造体を作成
type Cat struct {
    Animal       // これでAnimalの構造体を埋め込む
    FavoriteFish string
}

// Speakメソッドを作成
func (a Animal) Speak() {
    fmt.Printf("%s の鳴き声:%s\n", a.Name, a.Voice)
    //fmt.Printf("%s says: %s", a.Name, a.Voice)
}

func main() {
    // dogのインスタンスを作成
    dog := Dog{
        Animal: Animal{Name: "いっぬ", Voice: "いっぬ!いっぬ!!"},
        Breed:  "柴犬",
    }
    // Catのインスタンスを作成
    cat := Cat{
        Animal: Animal{Name: "ねっこ", Voice: "ねっこ!ねっこ!"},
        FavoriteFish: "大トロ",
    }
    // Dogのフィールドとメソッドを利用
    fmt.Println("Dogの名前:", dog.Name)
    fmt.Println("Dogの鳴き声:", dog.Voice)
    fmt.Println("Dogの犬種:", dog.Breed)
    dog.Speak()

    // Catのフィールドとメソッドを利用
    fmt.Println("Catの名前:", cat.Name)
    fmt.Println("Catの鳴き声:", cat.Voice)
    fmt.Println("Catの好きな食べ物:", cat.FavoriteFish)
    cat.Speak()
}

結果

Dogの名前: いっぬ
Dogの鳴き声: いっぬ!いっぬ!!
Dogの犬種: 柴犬
いっぬ の鳴き声
Catの名前: ねっこ
Catの鳴き声: ねっこ!ねっこ!
Catの好きな食べ物: 大トロ
ねっこ の鳴き声

まとめ

あと私がGoやった時は、この辺りの整理が一日でできなかったのでこれがちょうどいいボリュームだと思っています

私がインターフェースと構造体の話を書くのは、もう少し後のお話...