🚚 Moved code from Gists
This commit is contained in:
commit
f7ad2f122f
|
@ -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.
|
|
@ -0,0 +1,4 @@
|
||||||
|
// Package incrementor предназначен для работы с инкрементными числовыми значениями.
|
||||||
|
//
|
||||||
|
// Поведение инкрементных чисел может быть индивидуально настроено, а их использование в горутинах безопасно.
|
||||||
|
package incrementor
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
module incrementor
|
||||||
|
|
||||||
|
go 1.14
|
||||||
|
|
||||||
|
require github.com/stretchr/testify v1.6.1
|
|
@ -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=
|
|
@ -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()
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue