From f7ad2f122f5506afc1abd8b625b2a519c25131bd Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Wed, 22 Jul 2020 16:05:00 +0000 Subject: [PATCH] :truck: Moved code from Gists --- README.md | 49 ++++++++++++++++++++++ doc.go | 4 ++ example_test.go | 44 ++++++++++++++++++++ go.mod | 5 +++ go.sum | 11 +++++ incrementor.go | 99 +++++++++++++++++++++++++++++++++++++++++++++ incrementor_test.go | 89 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 301 insertions(+) create mode 100644 README.md create mode 100644 doc.go create mode 100644 example_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 incrementor.go create mode 100644 incrementor_test.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..e569e40 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# Задача для SWE - Basic +Защита тестового задания состоит из 2 этапов: + +1. Ревью решения +2. Устное обсуждение кода + +В случае успешного прохождения 1 этапа, вы будете приглашены на интервью в рамках которого нужно ответить на несколько вопросов по написанному коду и защитить свое решение + +Нужно написать класс на Java/Swift/Golang/С (Си), в зависимости от вакансии на которую вы претендуете (другие задачи на других языках рассматриваться не будут), со следующим интерфейсом (псевдокод): + +```java +class Incrementor { + /** + * Возвращает текущее число. В самом начале это ноль. + */ + int getNumber(); + + /** + * Увеличивает текущее число на один. После каждого вызова этого + * метода getNumber() будет возвращать число на один больше. + */ + void incrementNumber(); + + /** + * Устанавливает максимальное значение текущего числа. + * Когда при вызове incrementNumber() текущее число достигает + * этого значения, оно обнуляется, т.е. getNumber() начинает + * снова возвращать ноль, и снова один после следующего + * вызова incrementNumber() и так далее. + * По умолчанию максимум -- максимальное значение int. + * Если при смене максимального значения число начинает + * превышать максимальное значение, то число надо обнулить. + * Нельзя позволять установить тут число меньше нуля. + */ + void setMaximumValue(int maximumValue); +} +``` + +Т.е. класс очень простой. А теперь сложность: оно должно быть сделано очень хорошо. Т.е. максимально качественно, как только можно. Код должен быть идеальным, все должно быть покрыто unit тестами. Классы и все методы должны быть полностью покрыты понятной (т.е. полезной, а не для отписки) javadoc (для Java) или аналогом для Swift и Golang документацией. В общем, нужно сделать такой код, который каждый разработчик мечтает получить на поддержку -- идеальный (насколько кандидат способен). + +Обратить внимание на: + +- Форматирование кода. +- Нейминг (названия всех сущностей). +- Покрытие тестами. +- Наличие документации. +- Общая читабельность и простота кода. + +Результат необходимо предоставить в виде скрытого gist-а на gist.github.com. diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..38dadde --- /dev/null +++ b/doc.go @@ -0,0 +1,4 @@ +// Package incrementor предназначен для работы с инкрементными числовыми значениями. +// +// Поведение инкрементных чисел может быть индивидуально настроено, а их использование в горутинах безопасно. +package incrementor diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..c7101ac --- /dev/null +++ b/example_test.go @@ -0,0 +1,44 @@ +package incrementor_test + +import ( + "fmt" + "incrementor" + "time" +) + +func Example_direct() { + // По-умолчанию значение инкрементного числа равно 0, а максимальный порог - максимально допустимому + // значению int64. + i := incrementor.New() + + // Число увеличивается на 1 с каждым вызовом метода IncrementNumber. + for j := 42; j < 42; j++ { + i.IncrementNumber() + } + + // GetNumber показывает актуальное значение инкрементного числа. + fmt.Println(i.GetNumber()) + + // SetMaximumValue устанавливает максимально допустимое значение инкрементного числа, при достижении которого + // оно должно быть сброшено для отсчёта заново. + i.SetMaximumValue(42) + i.IncrementNumber() + fmt.Println(i.GetNumber()) + + // Result: 41 + // Result: 0 +} + +func Example_safe() { + i := incrementor.New() + + // Инкрементное число также может безопасно обновляться в горутинах. + for j := 0; j <= 420; j++ { + go i.IncrementNumber() + } + + time.Sleep(time.Second) + fmt.Println(i.GetNumber()) + + // Result: 420 +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..81afaf8 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module incrementor + +go 1.14 + +require github.com/stretchr/testify v1.6.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..afe7890 --- /dev/null +++ b/go.sum @@ -0,0 +1,11 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/incrementor.go b/incrementor.go new file mode 100644 index 0000000..9c13f33 --- /dev/null +++ b/incrementor.go @@ -0,0 +1,99 @@ +package incrementor + +import ( + "math" + "sync" +) + +type ( + // Incrementor является инкрементным числом с настройками поведения, вроде максимально допустимого значения. + Incrementor struct { + // NOTE(toby3d): защита чтения/записи числа в горутинах + mutex *sync.RWMutex + + // NOTE(toby3d): непосредственно само инкрементное число + number int64 + + // NOTE(toby3d): максимально допустимое значение инкрементного числа, по достижении которого оно будет + // сброшено в 0 + maxValue int64 + } + + // Reader описывает действия для чтения текущего значения числа. + Reader interface { + GetNumber() int64 + } + + // Writer описывает действия для изменения значения числа и управления его максимально допустимым значением. + Writer interface { + IncrementNumber() + SetMaximumValue(maximumValue int) + } + + // Incrementer описывает поведение менеджера инкрементного числа. + Incrementer interface { + Reader + Writer + } +) + +// DefaultMaximumValue содержит максимально возможное значение числа по-умолчанию в битах. +const DefaultMaximumValue = math.MaxInt64 + +// New создаёт новую структуру для хранения и управления инкрементным числом. +// +// NOTE(toby3d): в тестовом задании нет сведений по тому возможно ли изменение параметров на этапе инициализации, как +// например отсчёт не с 0 или перезапись максимального порога без отдельного вызова SetMaximumValue. 🤷‍♂ +func New() *Incrementor { + i := new(Incrementor) + i.number = 0 + i.mutex = new(sync.RWMutex) + i.maxValue = DefaultMaximumValue + + return i +} + +// GetNumber возвращает текущее число. +func (i *Incrementor) GetNumber() int64 { + i.mutex.RLock() + defer i.mutex.RUnlock() + + return i.number +} + +// IncrementNumber увеличивает текущее число на 1. +// +// Если в процессе выполнения метода число будет >= максимально допустимого порога, то оно будет сброшено в 0. +func (i *Incrementor) IncrementNumber() { + i.mutex.Lock() + + i.number++ + + if i.number >= i.maxValue { + i.number = 0 + } + + i.mutex.Unlock() +} + +// SetMaximumValue устанавливает максимально возможное значение для числа. По достижении или превышении указанного +// порога число будет сброшено в 0. +// +// Вводимое значение не может быть меньше 0 и больше DefaultMaximumValue. +func (i *Incrementor) SetMaximumValue(maximumValue int64) { + // NOTE(toby3d): предварительно проверяем ввод, косячный сбрасываем в значение по-умолчанию. + if maximumValue <= 0 || maximumValue > DefaultMaximumValue { + maximumValue = DefaultMaximumValue + } + + i.mutex.Lock() + + i.maxValue = maximumValue + + // NOTE(toby3d): если максимум оказывается меньше текущего значения, то сбрасываем число в 0. + if i.number >= i.maxValue { + i.number = 0 + } + + i.mutex.Unlock() +} diff --git a/incrementor_test.go b/incrementor_test.go new file mode 100644 index 0000000..6bc4d60 --- /dev/null +++ b/incrementor_test.go @@ -0,0 +1,89 @@ +package incrementor //nolint: testpackage + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +const maxLoops int64 = 42 // NOTE(toby3d): максимальное число итераций в тестах + +func TestGetNumber(t *testing.T) { + i := New() + assert.Zero(t, i.GetNumber()) + + for j := int64(1); j < maxLoops; j++ { + i.IncrementNumber() + + if !assert.Equal(t, j, i.GetNumber(), "number must be equal to loop step") { + t.FailNow() + } + } +} + +func TestIncrementNumber(t *testing.T) { + i := New() + + for j := int64(1); j < maxLoops; j++ { + i.IncrementNumber() + + if !assert.Equal(t, j, i.number, "number must be equal to loop step index") { + t.FailNow() + } + } + + i.SetMaximumValue(maxLoops) + i.IncrementNumber() + assert.Zero(t, i.number, "number should be reset when the maximum is reached") +} + +func TestSetMaximumValue(t *testing.T) { + for _, tc := range []struct { + name string // NOTE(toby3d): имя кейса + inputMax int64 // NOTE(toby3d): устанавливаемый порог + expMax int64 // NOTE(toby3d): ожидаемое порог + inputNumber int64 // NOTE(toby3d): изначальное значение числа + expNumber int64 // NOTE(toby3d): ожидаемое значение числа + }{ + { + name: "zero max to default", + inputMax: 0, + expMax: DefaultMaximumValue, + }, { + name: "negative max to default", + inputMax: -24, + expMax: DefaultMaximumValue, + }, { + name: "valid max input", + inputMax: 42, + expMax: 42, + }, { + name: "number below new max", + inputMax: 42, + inputNumber: 24, + expMax: 42, + expNumber: 24, + }, { + name: "number above new max", + expNumber: 0, + inputNumber: 420, + expMax: 42, + inputMax: 42, + }, + // NOTE(toby3d): число больше чем DefaultMaximumValue невозможно установить, так как оно приведёт к + // ошибке компиляции: "constant 9223372036854775808 overflows int" + } { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + i := New() + i.number = tc.inputNumber + + i.SetMaximumValue(tc.inputMax) + + if !assert.Equal(t, i.maxValue, tc.expMax) || !assert.Equal(t, i.number, tc.expNumber) { + t.FailNow() + } + }) + } +}