🚚 Moved code from Gists

This commit is contained in:
Maxim Lebedev 2020-07-22 16:05:00 +00:00
commit f7ad2f122f
No known key found for this signature in database
GPG Key ID: F8978F46FF0FFA4F
7 changed files with 301 additions and 0 deletions

49
README.md Normal file
View File

@ -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.

4
doc.go Normal file
View File

@ -0,0 +1,4 @@
// Package incrementor предназначен для работы с инкрементными числовыми значениями.
//
// Поведение инкрементных чисел может быть индивидуально настроено, а их использование в горутинах безопасно.
package incrementor

44
example_test.go Normal file
View File

@ -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
}

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module incrementor
go 1.14
require github.com/stretchr/testify v1.6.1

11
go.sum Normal file
View File

@ -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=

99
incrementor.go Normal file
View File

@ -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()
}

89
incrementor_test.go Normal file
View File

@ -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()
}
})
}
}