虎の穴開発室ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

Goを勉強したときに戸惑った仕様3選

本記事は2023 夏のブログ連載企画6日目の記事になります。

6月30日は、S.Aさんの「リモートワークはいいぞ!」でした。
明日はiwadyさんの「私的 Pythonライブラリ2選 ~私、気になります!~」になります。ご期待ください!

推しの言語

とらのあなラボでエンジニアをしているT.Hです。 自分の推し開発言語は「Go」になります。

社内勉強会でGoが取り上げられており、シンプルな構文を持ちつつもガベージコレクタ、並列処理、マルチプラットフォーム対応など最新の機能を備えていることに魅力を感じ、趣味の開発言語として採用することにしました。

ただ、今までメインで利用している言語がJavaだったため、Go独特の仕様に首を傾げることもありました。 今回はGoを勉強し始めた時に、ちょっと戸惑った仕様について紹介します。

その1.日付フォーマットの指定方法

Goでは日付のフォーマットに数字を利用します。 例えば「2023年01月01日 00時00分00秒」の形式で時刻を表示しようとした場合、Javaでは以下のようなフォーマットを利用します。

import java.text.SimpleDateFormat;
import java.util.Date;

public class CurrentTimeExample {
    public static void main(String[] args) {
        // 現在時刻を取得
        Date currentDate = new Date();
        
        // 日付フォーマットを指定
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH時mm分ss秒");
        
        // フォーマットに従って時刻を文字列化
        String formattedDate = dateFormat.format(currentDate);
        
        // 時刻を表示
        System.out.println(formattedDate);
    }
}

Goで同じ処理を行うと、以下のようになります。

package main

import (
    "fmt"
    "time"
)

func main() {
    // 現在時刻を取得
    currentTime := time.Now()

    // 日付フォーマットを指定
    format := "2006-01-02 15:04:05"

    // フォーマットに従って時刻を文字列化
    formattedTime := currentTime.Format(format)

    // 時刻を表示
    fmt.Println(formattedTime)
}

実行結果はこちらになります。

Goでは書式指定用のformat変数に時刻そのものを代入しているように見えますが、この部分がGoのフォーマットになります。 分解してみると

  1. 2006 : 年の部分を指定
  2. 01:月の部分を指定
  3. 02:日付の部分を指定
  4. 15:時の部分を指定(12時間表記の場合は03)
  5. 04:分の部分を指定
  6. 05:秒の部分を指定

となります。

これはアメリカ式の時刻順序に沿って仕様を決めたため、このような仕様になっているとのことでした。 (アメリカ式の時刻の順序は1月2日3時4分5秒2006年となるため)

参考:
Go time Package
https://pkg.go.dev/time#Time.Format

Go言語 - 日時のフォーマット処理
https://blog.y-yuki.net/entry/2017/05/25/000000

その2.大文字/小文字から始める名称について

Goでは名称の先頭が大文字で始まるか、小文字で始まるかによって公開範囲が異なります。

・先頭が大文字から始まる:全体公開
・先頭が小文字から始まる:非公開

簡単な構造体の利用を例に取り、動作を確認してみます。 main.goから、sub_packages/type.goを利用するプログラムになっています。 ディレクトリ構成は以下のようになっています。

main.goの内容は以下の通りです。

package main

import (
    "fmt"

    "sample/sub_packages"
)

func main() {
    person := sub_packages.Person{Name: "とらラボ"}
    fmt.Println(person.Name)
}

sub_packages/type.goの内容は以下の通りです。

package sub_packages

type Person struct {
    Name string
}

上記のプログラムを実行すると「とらラボ」と表示されます。

Person構造体のNameを「name」と小文字始まりに変更すると、以下のエラーが発生します。

公開範囲が非公開となったことでname変数にアクセスできなくなったことが原因になります。 このようにGoでは名称の始まりが大文字、小文字で公開範囲が変わってくるため注意が必要です。

参考:
Go の大文字小文字による公開範囲とオブジェクト指向言語のアクセス修飾子の違い
https://zenn.dev/msksgm/articles/20220527-go-package-scope

3.クラスはないけど、クラスっぽいことができる

Goにクラスに相当する機能はありませんが、似たような機能を実現する仕組みが用意されています。

まずはJavaのサンプルプログラムをご覧ください。

public class Main {
    public static class Person {
        private String name;

        public Person(String name) {
            this.name = name;
        }

        public void showName() {
            System.out.println("こんにちは " + name);
        }
    }

    public static void main(String[] args) {
        Person p = new Person("とらラボ");
        p.showName();
    }
}

Go言語で同様の処理を行う場合、以下のようなプログラムになります。

package main

import "fmt"

type Person struct {
    Name string
}

func (person Person) showName() {
    fmt.Println("こんにちは " + person.Name)
}

func main() {
    p := Person{Name: "とらラボ"}
    p.showName()
}

実行結果はこちらになります。

showName関数の定義時、レシーバ((person Person)の部分)を加えることで、構造体のフィールドにアクセス可能になります。 レシーバには

・ポインタレシーバ:構造体の値を書き換えることができる。
・値レシーバ:コピーした値が利用されるため、構造体の値を書き換えることはできない。

という違いがあり、用途に応じて使い分ける必要があります。

参考:
Go - メソッドとレシーバ
https://qiita.com/Yuuki557/items/e9f5bdfbbfe92973a05e

終わりに

自分がGoを学び始めた時に戸惑った仕様について紹介させていただきました。

今回紹介させていただいた内容のように他言語と比較して少し変わった仕様も存在しているため、 他言語の考え方をそのままGoに持ち込んでしまうとハマったり戸惑ったりしてしまうと思います。

少し特殊な仕様がありつつも、Go自体はシンプルな構文を持ち、学習しやすい言語だと感じています。 本記事で少しでもGoに興味を持っていただけたら幸いです。

採用情報

虎の穴では一緒に働く仲間を募集中です!
この記事を読んで、興味を持っていただけた方はぜひ弊社の採用情報をご覧下さい。
カジュアル面談やエンジニア向けイベントも随時開催中です。ぜひチェックしてみてください♪
yumenosora.co.jp