Files
go-synapse-backupper/.agents/skills/golang/golang-tswf-codestyle-skill/SKILL.md
T

8.7 KiB

name, description
name description
golang-tswf-codestyle-skill ТЫ ДОЛЖЕН использовать этот скилл для всех Go проектов, модуль которых содержит подстроку tswf.io или если пользователь явно выбрал этот скилл.

Naming Conventions

Базовые инструкции

  • Запрещено использовать короткие имена - i, j, k, temp и так далее - под запретом. Все переменные и методы должны иметь осмысленные имена.
  • Локальные переменные - camelCase, :=
  • Слайсы - всегда инициализируются через make.
  • Поля переменные - camelCase
  • Function Receiver - первая буква структуры, владелец по указателю.
  • Структура, скрытая за интерфейсом - camelCase с маленькой буквы (myPrivateStuct)
  • Публичная структура, например Dto - camelCase с большой буквы (MyPublicStruct)
type Something struct {}

func (s /*<--- первая буква слова Something*/ *Something) somethingFunction() {  }

Пакеты в именах типов

Старайся использовать пакет, как часть имени класса, а классы старайся раскладывать по пакетам как можно более по смыслу, но не уходя в спагетти код

Плохой пример

dtos.SomethingApiUserDto

Хороший пример

somethingapi.UserDto

Лаконичные, достаточные названия

Если класс что-то делает, а так чаще всего - старайся вложить это в название.

Например класс для регистрации - Registrar, а класс для продвинутого конфигурирования чего-то - AdvancedConfigurer.

Если ты видишь, что тебе в имени нужно указать что-то слишком длинное, вроде SomethingApiUserDtoMapperToOurEntity, то постарайся закрыть вопрос контекста в имени класса его пакетом и придумай название получше.

Interfaces

Ты ДОЛЖЕН скрывать компоненты за интерфейсами.

Это позволит подстраховаться от жесткой связанности на структуру.

Например:

ПЛОХОЙ пример

type MyComponent struct {
  // ...
}

func NewMyComponent() *MyComponent {}

ПРАВИЛЬНЫЙ ПРИМЕР

type MyComponent interface {
  // Все нужные публичные методы из myComponent
}

type myComponent struct {
  // ...
}

// Этот пример правильный - используется интерфейс
func NewMyComponent() MyComponent {
  return &myComponent{}
}

Даже в рамках одного пакета при возможности используй интерфейсы вместо структур для инъекций компонентов

Инстанцирование

Все новые объекты создаются через конструктор.

Конструктор принимает все объекты зависимости и внедряет их в создаваемый объект прямо при конструировании.

Если возникает циклическая зависимость, то можно применить паттерн "Фасад"

Плохой пример

type MyComponent interface{ 
  SetDependency1(dep Dependency1)
  SetDependency2(dep Dependency2)
}

// Плохой пример
// Конструктор не гарантирует полного инстанцирования объекта. 
// Такой конструктор может вернуть объект в некорректном состоянии (если вызывающий код сам не использует сеттеры для зависимостей)
func NewMyComponent() MyComponent { /** ... **/ }

Хороший пример

type MyComponent interface{ 

}

// Хороший пример
// Конструктор явно принял все зависимости
// Параметры конструктора для читаемости каждый на новой строке
func NewMyComponent(
  dep1 Dependency1,
  dep2 Dependency1,
) MyComponent
{ 
  /** ... **/
}

Перенос строк

Для читаемости кода человеком и улучшения его визуальной структуры в коде применяются переносы строк.

Ты можешь применять их более креативно, но приведу два кейса: вложенность и много параметров функции

Вложенность

Если вызовы вложены один в другой, то человеку-читателю легко потерять структуру. Например:

Плохой пример

// Вся цепочка смешивается в кашу.
// Сразу не видно, кто в кого вложен. Читателю приходится напрягаться просто чтобы это понять. Это очень плохой пример
SomeMethod1(SomeMethod2(SomeMethod3()), SomeMethod4(&myStruct{1, 2, 3}))

Хороший пример

// Вся цепочка вызовов структурно сразу визуализируется глазами читателя. 
// Видно, кто в кого вложен. Это хороший пример
SomeMethod1(
  SomeMethod2(
    SomeMethod3(),
  ), 
  SomeMethod4(
    &myStruct{1, 2, 3},
  ),
)

Множество параметров функции

Если у функции есть параметры с длинными именами, типами или просто занимающими место - используй перенос строк, чтобы наглядно разделить их.

Плохой пример

// Все в кучу. Чем больше параметров, тем тяжелее читать человеку
// Это плохой пример.
func funcOne(a string, b int, c bool, g pos.Position, e user.Controller) {}

func main() {
  funcTwo("hello world", 1, false, resolvePos().absolute(), resolveController())
}

Хороший пример

// Четко видно структуру. Человеку просто такое читать.
// Это хороший пример.
func funcOne(
  a string,
  b int,
  c bool, 
  g pos.Position, 
  e user.Controller,
) {}

func main() {
  funcTwo(
    "hello world", 
    1,
    false, 
    resolvePos().absolute(), 
    resolveController(),
  )
}

Project Structure

Обязательно для любого Go проекта

  • Makefile с кросс-платформенной сборкой
  • README.md + русская версия
  • Весь код лежит в папках pkg, cmd или resources ( код для доступа к embed ресурсам )
  • Папка bin - туда попадают собранные через Make бинари. Она в gitignore

Для микросервисов

Если это сервис, то добавляется

  • Dockerfile
  • Docker-compose
  • k8s chart

CLI приложение

  • Папка cmd, в подпапках которой реализуются консольные команды на cobra(!)

Структура файлов

  • Root
    • bin # ОБЯЗАТЕЛЬНО в .gitignore!!
    • doc
    • deploy
      • docker (тут если делаешь Docker, то добавь сразу compose. и посмотри релевантные скиллы для этого)
      • k8s
      • e.t.c.
    • pkg
      • domain
      • adapters
      • infrastructure
    • cmd
      • <cmdname>
    • resources
      • <embed go resources>
    • go.mod
    • go.sum
    • README.md
    • README.ru.md
    • .gitignore
    • Makefile

Имя модуля

Спроси имя модуля проекта, если пользователь явно его не задал. Если пользователь дал базовый путь (он оканчивается на /), то прибавь к нему имя корневой папки проекта