Avatar ·

Как правильно организовать структуру многофайлового проекта на Go?

Всем привет! Я начал изучать Go недавно, и у меня возникла проблема с организацией проекта, который состоит из нескольких файлов. Я написал небольшое консольное приложение для управления задачами (todo list). В одном файле main.go у меня лежит весь код: и обработка ввода, и логика, и работа с файлами. Когда проект вырос до 500 строк, стало очень неудобно в нём ориентироваться. Я пытался разбить код на несколько файлов, но у меня ничего не получилось. Например, я создал папку `tasks`, положил туда файл `tasks.go` с функциями для работы с задачами, а в `main.go` пытаюсь их вызвать. Но компилятор ругается, что функции не определены. Вот пример моего кода: ```go // main.go package main func main() { task := CreateTask("Купить молоко") // Ошибка: undefined: CreateTask fmt.Println(task) } ``` ```go // tasks/tasks.go package tasks type Task struct { Title string } func CreateTask(title string) Task { return Task{Title: title} } ``` Я не понимаю, как правильно импортировать функции из другого файла. В Java или Python это делается просто import'ом, а в Go у меня ничего не выходит. Подскажите, как правильно организовать многофайловый проект? Как разложить код по пакетам? Нужно ли использовать go modules? Буду благодарен за подробное объяснение с примерами. <<>>

Основная проблема: пакеты и импорты в Go

Вы столкнулись с классической ошибкой новичков. В Go код организуется через пакеты, а не просто через файлы в папках. Ваш код в tasks/tasks.go объявляет пакет package tasks, а main.go — пакет main. Чтобы использовать функции из одного пакета в другом, их нужно импортировать.

Шаг 1: Инициализируйте модуль

Go требует явного указания модуля. Это аналог package.json в Node.js или requirements.txt в Python.

go mod init todo-app

После этой команды появится файл go.mod с содержимым:

module todo-app

go 1.21

Шаг 2: Исправьте структуру папок и импорты

Ваша структура должна выглядеть так:

todo-app/
├── go.mod
├── main.go
└── tasks/
    └── tasks.go

Теперь исправьте main.go:

package main

import (
    "fmt"
    "todo-app/tasks"  // Импорт пакета tasks из модуля todo-app
)

func main() {
    task := tasks.CreateTask("Купить молоко") // Обратите внимание: tasks.CreateTask
    fmt.Println(task.Title)
}

А в tasks/tasks.go нужно экспортировать структуру и функцию (с большой буквы):

package tasks

type Task struct {
    Title string
}

func CreateTask(title string) Task {
    return Task{Title: title}
}

Шаг 3: Правила экспорта в Go

  • Имена с большой буквы — экспортируются (видны из других пакетов).
  • Имена с маленькой буквы — приватные (видны только внутри своего пакета).
  • Ваша функция CreateTask экспортируется, потому что начинается с заглавной буквы.

Полный рабочий пример

main.go

package main

import (
    "fmt"
    "todo-app/tasks"
)

func main() {
    task1 := tasks.CreateTask("Купить хлеб")
    task2 := tasks.CreateTask("Сделать ДЗ")
    
    fmt.Printf("Задача 1: %s\n", task1.Title)
    fmt.Printf("Задача 2: %s\n", task2.Title)
}

tasks/tasks.go

package tasks

import "fmt"

type Task struct {
    Title    string
    Complete bool
}

func CreateTask(title string) Task {
    return Task{Title: title}
}

func (t *Task) MarkComplete() {
    t.Complete = true
    fmt.Printf("Задача '%s' выполнена!\n", t.Title)
}

Дополнительные советы

  1. Один пакет — одна папка. Не кладите два разных пакета в одну папку.
  2. main.go должен быть в корневой папке проекта (не в подпапке).
  3. Имена пакетов обычно совпадают с именем папки (tasks → пакет tasks).
  4. Если файлы в одной папке, они должны иметь одинаковый package.

После всех изменений запустите:

go run main.go

Или, если хотите собрать бинарник:

go build -o todo-app
<<>>

Альтернативный подход: всё в одном пакете, но в разных файлах

Если ваш проект небольшой (до 1000 строк) и не предполагает переиспользования кода в других проектах, можно обойтись без создания отдельных пакетов. Просто разложите код по разным файлам в одной папке с одинаковым package main.

Пример структуры:

todo-app/
├── main.go      // точка входа, вызовы
├── tasks.go     // структуры и функции для задач
└── storage.go   // работа с файлами/БД

main.go

package main

func main() {
    task := CreateTask("Купить молоко")
    SaveTask(task)
}

tasks.go

package main

type Task struct {
    Title string
}

func CreateTask(title string) Task {
    return Task{Title: title}
}

storage.go

package main

import "fmt"

func SaveTask(t Task) {
    fmt.Printf("Сохранена задача: %s\n", t.Title)
}

В этом случае не нужно импортировать — все функции и типы видны во всех файлах пакета main. Компиляция:

go run .

Когда использовать:

  • Прототипирование и маленькие утилиты
  • Учебные проекты
  • Когда код не нужно переиспользовать

Минусы:

  • Нет чёткого разделения ответственности
  • Риск конфликтов имён при разрастании проекта
  • Нельзя переиспользовать код в других проектах