Go тілі: нөлден өз блогыңызға дейін 15-сабақ (барлығы 50)
Go-дағы пакеттер мен модульдер: go mod және бас әріп
Go курсының он бесінші сабағы. Пакет — бұл қалта, ал аттағы бас әріп — сыртқа ашылатын есік. Бағдарламаны қалталарға қалай бөледі, go.mod пен go.sum ішінде не жазылған, бөтен пакетті go get арқылы қалай қосады және go mod tidy-ды неге код жіберер алдында орындайды.
Не үшін керек
main.go ұлғайып кетті: тип, әдістер, қойма, қателер — бәрі бір файлда. Бөлетін уақыт келді.
Сабақтың екінші жартысы: ерте ме, кеш пе, сізге бөтен код керек болады. Идентификатор генераторын не JSON талдауын өзіңіз жазу — мыңдаған адам жазып, тексеріп қойғанда, өмірді босқа шығындау.
Бірден тұтас
Жаңа sabaq-14 қалтасы, оның ішінде тағы біреуі — blog. Үш файл:
sabaq-14/
go.mod
main.go
blog/
article.go
store.go
blog/article.go:
package blog
import "fmt"
type Article struct {
Title string
Words int
}
func (a Article) ReadingTime() int {
return (a.Words + 199) / 200
}
func (a Article) String() string {
return fmt.Sprintf("%s (%d мин)", a.Title, a.ReadingTime())
}
blog/store.go:
package blog
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("мақала табылмады")
type Store struct {
items map[string]Article
}
func NewStore() *Store {
return &Store{items: map[string]Article{}}
}
func (s *Store) Add(slug string, a Article) {
s.items[slug] = a
}
func (s *Store) Get(slug string) (Article, error) {
a, ok := s.items[slug]
if !ok {
return Article{}, fmt.Errorf("get %q: %w", slug, ErrNotFound)
}
return a, nil
}
func (s *Store) Len() int { return len(s.items) }
main.go:
package main
import (
"errors"
"fmt"
"github.com/google/uuid"
"sabaq14/blog"
)
func main() {
s := blog.NewStore()
s.Add("dala", blog.Article{Title: "Дала туралы", Words: 400})
a, err := s.Get("dala")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(a)
fmt.Println("мақала саны:", s.Len())
if _, err := s.Get("kokek"); errors.Is(err, blog.ErrNotFound) {
fmt.Println("404:", err)
}
fmt.Println("жаңа id:", uuid.NewSHA1(uuid.NameSpaceURL, []byte("shanraq.org/dala")))
}
Үш пәрмен ретімен:
go mod init sabaq14
go get github.com/google/uuid
go run .
Іске қоспас бұрын — төменге қарамай, бағдарламаның не басып шығаратынын дауыстап айтыңыз. Содан кейін жіберіп, салыстырыңыз.
Дала туралы (2 мин)
мақала саны: 1
404: get "kokek": мақала табылмады
жаңа id: 7046abf7-a993-54e5-8960-558b4e3d88d9
Идентификатор сізде де дәл сондай болады: ол кездейсоқтықтан емес, мекенжайдан есептелген.
Талдау
Пакет — бұл қалта
Бір қалтадағы файлдардың бәрі бір пакетке тиесілі, әрқайсысының бірінші жолы бірдей: package blog. Пакетті файлдарға қалауыңызша бөлуге болады — компиляторға бәрібір, ал адамға мағынасы бойынша ыңғайлы: тип бір файлда, қойма екіншісінде.
Пакеттің аты әдетте қалтаның атымен сәйкес келеді. Пакет ішінде файлдар бір-бірін толық көреді, ешқандай импортсыз: store.go көрші файлдағы Article типін пайдаланады да, ол үшін ештеңе істемейді.
Елестетіп көріңіз. Үйдегі бөлме. Бөлменің ішінде бәрі қол астында. Сыртқа, дәлізге, тек өзіңіз есікке шығарып қойғаныңыз ғана шығады.
Бас әріп — сыртқа ашылатын есік
Енді құрылымдар сабағында өрістердің неге Title және Words деп аталғаны түсінікті.
Ереже біреу және ерекшеліксіз: бас әріптен басталатын ат басқа пакеттерден көрінеді, кіші әріптен басталатыны — жоқ. Бұл бәріне қатысты: типтерге, функцияларға, әдістерге, өрістерге, айнымалыларға.
Біздің қоймадағы items өрісі кіші әріппен жазылған, бұл кездейсоқ емес. main.go ішінен көріңіз:
len(s.items)
Бағдарлама жиналмайды:
s.items undefined (cannot refer to unexported field items)
Дәл сол себепті Store-да Len() әдісі бар: сыртқа құрылысы емес, білігі шығады. Ертең сөздіктің орнына дерекқор қойғыңыз келсе — сырттан ештеңе сынбайды.
go mod init және go.mod ішінде не бар
go mod init sabaq14
Бұл пәрмен модульді жариялайды — жинауға және жариялауға болатын бірлікті. Модуль — түбірінде go.mod файлы бар қалталар ағашы; пакеттер — соның ішіндегі қалталар.
go get-тен кейін файл былай көрінеді:
module sabaq14
go 1.27.1
require github.com/google/uuid v1.6.0
Бірінші жол — модульдің аты, ол ішкі импорт жолдарының да басы. Екіншісі — сіз жинап отырған тіл нұсқасы; сізде өзгеше болады. Үшіншісі — басқалардан алғаныңыздың нақты нұсқаларымен берілген тізімі.
Өз пакетіңізді импорттау
import "sabaq14/blog"
Жол модульдің атынан басталады, одан кейін — оның ішіндегі қалтаның жолы. Салыстырмалы ./blog та, дискідегі толық жол да емес: тек осылай.
import блогындағы ретке назар аударыңыз: алдымен стандартты кітапхана, сосын бөтен пакеттер, содан кейін өзіңіздікі, топтардың арасында бос жол. Бұл тілдің талабы емес, ортақ әдет, ал редактор сақтағанда бәрін өзі ретке келтіреді.
Бөтен пакет: go get, go.sum, go mod tidy
go get github.com/google/uuid үш іс істейді: пакетті жүктейді, go.mod-қа жол қосады және жүктелгеннің таңбасын go.sum-ға жазады.
go.sum — тәуелділіктер тізімі емес, бақылау сомаларының тізімі. Келесі жинауда Go жүктелгеннің жазылғанмен сәйкестігін тексереді. Екі файлды да репозиторийге салады.
Елестетіп көріңіз. Таңбасы бар қолхат. Алынған кітаптардың тізімі — бұл
go.mod. Алgo.sum— қайтарылғаны дәл сол кітап екенін, іші басқа мәтін жазылған ұқсас кітап емесін көрсететін белгі.
go mod tidy тәртіпке келтіреді: импорттап қойып, жариялауды ұмытқаныңызды қосады, ал енді қолданылмайтынын алып тастайды. Оны кодты жіберер алдында орындаңыз: go get-тен кейін бірден пакетіміз // indirect деп белгіленген еді, ал ол белгіні тек tidy алып тастады.
Пакеттің аты шақыру орнында оқылады
Сырттан blog.Article және blog.NewStore деп жазады. Сондықтан пакеттің ішінде оның атын қайталаудың қажеті жоқ: blog.BlogArticle — кекештену, ал blog.Article сөйлемдей оқылады.
Сол себепті пакет аттары қысқа әрі бір сөзді, кіші әріппен, астыңғы сызықсыз болады: http, fmt, errors.
internal/ — сырттан жабық қалта
Қалтаны internal деп атасаңыз, оның ішіндегіні тек сол модульдегі көршілері импорттай алады. Модуліңізді қосқан бөтен жоба оған жете алмайды — мұны компилятордың өзі тексереді.
Мұны бір минутта тексеруге болады. Екі модуль: біріншісінде пакет internal ішінде жатыр, екіншісі оны импорттауға тырысады.
$ cd lib && go build ./... # свой модуль читает свой internal
свой модуль: собралось
$ cd ../app && go build ./... # чужой модуль лезет в тот же пакет
package example.com/app
main.go:6:2: use of internal package example.com/lib/internal/store not allowed
Компилятор кеңес те бермейді, ескертпейді де — ол жинаудан бас тартады. internal — Go әдет емес, тіл ережесі деп түсінетін жалғыз қалта аты.
Пакеттеріңіз болғанын қалап, бірақ бөтен адамдарға олар өзгермейді деп уәде бергіңіз келмегенде ыңғайлы.
Елестетіп көріңіз. «Қызметтік бөлме» деп жазылған есік. Ол сол ғимаратта тұр, қызметкерге жабық емес, бірақ келуші оның ар жағына кірмейді.
Жоба ағашы: cmd, internal және pkg
Біздің блогтың ағашы жалпақ: main.go blog қалтасының қасында тұр, ешқандай cmd мен internal жоқ. Бұл сабақ үшін жеңілдету емес — Go-ның өзі осындай өлшемдегі бағдарламаға солай кеңес береді: модуль бір бағдарлама болып тұрғанда, артық қалта импорт жолдарын ұзартады да, ештеңе қоспайды.
Бөтен репозиторийлерде кездесетін үш ат:
cmd/— модульде бағдарлама біреуден көп болғанда.cmd/app,cmd/migrate,cmd/export: әрқайсысының өзmain-і бар, ал ортақ код қасында жатады әрі бәріне импортталады.internal/— сырттан импорттауға болмайтын нәрсе. Жаңа ғана өлшеген тіл ережесі.pkg/— бөтен жобаларға арналған код салынатын қалта. Бұл Go ережесі емес, қауымдастықтың бір бөлігінің әдеті: компиляторpkgтуралы ештеңе білмейді.
Осы сабақты оқып отырған сайт былай жіктелген: cmd/ ішінде төрт бағдарлама — app, migrate, export және adminctl, — internal/ ішінде бес пакет әрі pkg/ ішінде он жеті. Бұл ағаш жалпақтан өсті, әрі екінші бағдарлама пайда болғанда өсті, одан бұрын емес.
Курс блогы бұлар жоқ болғандықтан зардап шеге ме? Жоқ: онда бір бағдарлама әрі бір ортақ пакет бар, ал бұл аттардың кез келгені бос қалта қосар еді. Бірақ блогыңызда екінші бағдарлама пайда болғанда — мысалы, бөлек migrate, — оны қайда қою керегін білесіз.
Ең соңғысы. Қалталардан бастамаңыз. Файл біреу және онда екі жүз жол болса — сол күйінде қалдырыңыз. Бөлу керек кез — шекараның қайдан өтетінін көрген кез, одан бұрын емес.
Сабақ картасы
Өз сөзіңізбен айтыңыз
Қарамай, дауыстап немесе қағазға жазып жауап беріңіз. Жауаптары — сабақтың соңында.
- Екі файл бір бағдарламада жатқанымен,
main.goнегеs.items-ке жүгіне алмайды? go.modgo.sum-нан несімен өзгеше және екеуі не үшін керек?- Неге
blog.BlogArticleемес,blog.Article? internal/pkg/-дан несімен өзгеше?
Тапсырма
Міндетті. Қателер сабағындағы бағдарламаңызды алып, оны екі пакетке бөліңіз: тип, қойма және қателер бар blog — және тек шақыратын main. Деректері бар өрісті кіші әріппен қалдырып, Len() әдісін қосыңыз. go run . жұмыс істейтініне және main ішінен ішкі өріске жүгіну жиналмайтынына көз жеткізіңіз.
Қалауыңызша.
github.com/google/uuidпакетін қосып, әр мақалаға идентификатор беріңіз.go.modішіненrequireжолын өшіріп,go mod tidyорындаңыз. Не болатынын қараңыз.blogқалтасының атынinternal/blogдеп өзгертіп, импортты түзетіңіз. Бағдарлама бұрынғыдай жұмыс істеуі керек.
Блогта қайда тұрады
Бұл сайт та солай құрылған: сервер бір пакетте, мақалалар екіншісінде, қойма үшіншісінде, әрқайсысы қалғанымен бас әріппен жазылған бірнеше ат арқылы сөйлеседі. Осы сабақтан кейінгі блогтың күйі — step-6: дәл сол блог, қалталарға бөлінген.
Жауаптар
Жауаптарды көрсету
- Себебі
itemsкіші әріппен жазылған, демек текblogпакетінің ішінде көрінеді. Бағдарлама біреу, бірақ пакеттер бөлек, ал шекара аттың бірінші әрпі бойынша өтеді. СыртқаLen()әдісі шығады — құрылысы емес, білігі. go.mod— нені және қандай нұсқада алғаныңыздың тізімі; оны адам оқиды.go.sum— жүктелгеннің бақылау сомалары; оны Go оқиды, алмастыруды байқау үшін. Екі файл да репозиторийде сақталады.- Себебі пакеттің аты шақыру орнында оқылады: сырттан
blog.Articleболып шығады. Пакет атын ішінде қайталау — бөтен кодтың әр жолынан көрінетін кекештену. internal— тіл ережесі: ол жерге кірмек болған бөтен модуль жай ғана жиналмайды, әрі мұны компилятор айтады.pkg— кәдімгі қалта аты әрі қауымдастықтың бір бөлігінің әдеті; Go ол туралы ештеңе білмейді әрі онымен ештеңеге тыйым салмайды.
Пікірлер (0)
Пікір қалдыру үшін кіріңіз →
Әзірге пікір жоқ. Бірінші болыңыз.