Русский
Русский
English
Статистика
Реклама

Problem solving

Взламываем Ball Sort Puzzle

08.01.2021 12:15:36 | Автор: admin
Определение кружочков при помощи OpenCVОпределение кружочков при помощи OpenCV

Ball Sort Puzzle это популярная мобильная игра на IOS/Android. Суть её заключается в перестановке шариков до тех пор, пока в колбах не будут шарики одного цвета. При этом шарик можно перетаскивать либо в пустую колбу, либо на такой же шарик.

Так случилось, что я в неё залип. Очнулся примерно через месяц, на 725 уровне. Он мне никак не давался насколько бы глубоко я не пытался продумать свою стратегию. В итоге с этим вопросом я вышел в интернет, и заодно выяснил несколько интересных особенностей головоломки.

Во-первых, игра бесконечна почти бесконечна. По крайней мере уже сейчас на YouTube есть прохождения всех уровней в плоть до 5350, а в телеграмме гуляют скриншоты 10к+ уровней. Вторая особенность, и вот это уже некрасиво, не у всех уровней есть решение.

Ну это ни в какие ворота против нас играет коварный ИИ. Нужно действовать соответственно!

Под катом мы:

  • Придумаем алгоритм, решающий эту головоломку (Python)

  • Научимся парсить скриншот игры, чтобы скармливать алгоритму задачки (OpenCV)

  • Напишем телеграм бота, который будет принимать скриншоты и возвращать решения

  • Выстроим CI/CD через GitHub Actions и задеплоим бота на Яндекс.Функции

Погнали!


Алгоритмическое решение задачи

Решать такую задачу было весьма занимательно. Поэтому предлагаю заинтересованному читателю попробовать решить её самостоятельно.

Я же в первую очередь решил побить проблему на сущности. Это сделает алгоритм чуть более элегантным, а так же поможет в будущем парсить скриншоты игры:

class Color
class Color:    def __init__(self, symbol, verbose_name, emoji):        self.symbol = symbol        self.verbose_name = verbose_name        self.emoji = emoji    def __repr__(self) -> str:        return f'Color({self})'    def __str__(self) -> str:        return self.emoji
Beta-редактор хабра ломается на рендеринге emoji :poop:Beta-редактор хабра ломается на рендеринге emoji :poop:
class Ball
class Ball:    def __init__(self, color: Color):        self.color = color    def __eq__(self, other: 'Ball'):        return self.color is other.color    def __repr__(self):        return f'Ball({self.color.verbose_name})'    def __str__(self) -> str:        return str(self.color)
class Flask
class Flask:    def __init__(self, column: List[Color], num: int, max_size: int):        self.num = num        self.balls = [Ball(color) for color in column]        self.max_size = max_size    @property    def is_full(self):        return len(self.balls) == self.max_size    @property    def is_empty(self) -> bool:        return not self.balls    def pop(self) -> Ball:        return self.balls.pop(-1)    def push(self, ball: Ball):        self.balls.append(ball)    def __iter__(self):        return iter(self.balls)    def __getitem__(self, item: int) -> Ball:        return self.balls[item]    def __len__(self) -> int:        return len(self.balls)    def __str__(self) -> str:        return str(self.balls)
class Move
class Move:    def __init__(self, i, j, i_color: Color):        self.i = i        self.j = j        self.emoji = i_color.emoji    def __eq__(self, other: 'Move') -> bool:        return (self.i, self.j) == (other.i, other.j)    def __repr__(self) -> str:        return f'Ball({self})'    def __str__(self) -> str:        return f'{self.i} -> {self.j}'

Для решения будем использовать метод поиска с возвратом (Backtracking).

Решение задачи методом поиска с возвратом сводится к последовательному расширению частичного решения. Если на очередном шаге такое расширение провести не удается, то возвращаются к более короткому частичному решению и продолжают поиск дальше. Данный алгоритм позволяет найти все решения поставленной задачи, если они существуют.

В случае с нашей игрой это метод применяется так: мы рекурсивно обходим все возможные перестановки шариков (move) до тех пор, пока

  • Либо нас не выкинет наш критерий остановки решённый пазл

  • Либо в нашем хранилище состояний (states) не будет всех возможных перестановок в таком случае решения нет

    def solve(self) -> bool:        if self.is_solved:            return True        for move in self.get_possible_moves():            new_state = self.commit_move(move)            if new_state in self.states:  # Cycle!                self.rollback_move(move)                continue            self.states.add(new_state)            if self.solve():                return True            self.rollback_move(move)        return False

Алгоритм достаточно прямолинейный и далеко не всегда выдаёт оптимальное решение. Тем не менее он справляется с решением большинства задачек из игры за 1 сек.

Проверим алгоритм на чём-нибудь попроще:

def test_3x3():    data_in = [        [color.RED, color.GREEN, color.RED],        [color.GREEN, color.RED, color.GREEN],        [],    ]    puzzle = BallSortPuzzle(data_in)    result = puzzle.solve()    assert result is True    play_moves(data_in, puzzle.moves)
Алгоритм в действииАлгоритм в действии

Полная версия программы доступна на github.

Распознавание скриншотов игры

Мы будем работать с .jpg картинками двух видов

Скриншоты уровней игры Скриншоты уровней игры

Каждый чётный раунд игры состоит из 11 колб и 36 шариков, а нечётный 14 колб и 48 шариков. Чётные и нечётные раунды отличаются расположением колб, но на счастье всё остальное у них одинаковое по 4 шарика в колбе, 2 колбы пустые, цвета используются одни и те же.

Первое, что хочется сделать это обрезать рабочую область, оставив только колбы с шариками. В противном случае наша программа за шарики может принимать элементы управления, фон или рекламу. Для этого отрежем по четверти сверху и снизу изображения.

class ImageParser:    def __init__(self, file_bytes: np.ndarray, debug=False):        self.image_orig = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR)        self.image_cropped = self.get_cropped_image(self.image_orig)    @staticmethod    def get_cropped_image(image):        height, width, _ = image.shape        quarter = int(height / 4)        cropped_img = image[quarter : height - quarter]        return cropped_img
Рабочая областьРабочая область

Теперь будем искать кружочки. В библиотеке OpenCV ровно для этих целей существует метод HoughCircles. Чтобы его использовать нужно перевести изображение в чёрно-белый вид, а также "эмпирически подобрать" параметры поиска. Чтобы найденные кружочки потом расфасовать по колбам, нормализуем и отсортируем их.

    @staticmethod    def normalize_circles(circles):        last_y = 0        for circle in circles:            if math.isclose(circle[1], last_y, abs_tol=3):                circle[1] = last_y            else:                last_y = circle[1]        return circles    def get_normalized_circles(self) -> List[Any]:        image_cropped_gray = cv2.cvtColor(self.image_cropped, cv2.COLOR_BGR2GRAY)        circles = cv2.HoughCircles(image_cropped_gray, cv2.HOUGH_GRADIENT, 2, 20, maxRadius=27)        if circles is None:            raise ImageParserError("No circles :shrug:")        circles = np.round(circles[0, :]).astype("int16")        ind = np.lexsort((circles[:, 0], circles[:, 1]))        circles = circles[ind]        circles = self.normalize_circles(circles)        ind = np.lexsort((circles[:, 0], circles[:, 1]))        circles = circles[ind]        return circles
Отсортированные шарики слева-направо, сверху-внизОтсортированные шарики слева-направо, сверху-вниз

Дальше будем определять цвет шарика.

Из-за того, что Telegram жмёт картинки мы не можем просто взять цвет центрального пикселя он может быть артефактом компрессии. Поэтому найдём доминирующий цвет, тот который в кружочке встречается чаще всего.

    @staticmethod    def get_dominant_color(circle) -> Color:        colors, count = np.unique(circle.reshape(-1, circle.shape[-1]), axis=0, return_counts=True)        dominant = colors[count.argmax()]        return dominant
Найденные кружочки Найденные кружочки

Этот доминирующий цвет мы будем сравнивать с изначально заданными цветами, и искать ближайший. В данной задаче нам на помощь приходит Евклидово расстояние и теорема Пифагора: если представить цвет точкой в трёхмерном пространстве, то расстояние до любой другой точки этого пространства:

d = \sqrt{(r2-r1)^2 + (b2-b1)^2 + (g2-g1)^2}

Посчитаем такое расстояние до каждого из изначально заданных цветов и найдём минимальное

RBG_TO_COLOR = {    (147, 42, 115): VIOLET,    (8, 74, 125): BROWN,    (229, 163, 85): L_BLUE,    (68, 140, 234): ORANGE,    (196, 46, 59): BLUE,    (51, 100, 18): GREEN,    (35, 43, 197): RED,    (87, 216, 241): YELLOW,    (125, 214, 97): L_GREEN,    (123, 94, 234): PINK,    (16, 150, 120): LIME,    (102, 100, 99): GRAY,}COLORS = np.array(list(RBG_TO_COLOR.keys()))def get_closest_color(color: np.ndarray) -> Color:    distances = np.sqrt(np.sum((COLORS - color) ** 2, axis=1))    index_of_smallest = np.where(distances == np.amin(distances))    smallest_distance = COLORS[index_of_smallest].flat    return RBG_TO_COLOR[tuple(smallest_distance)]  # type: ignore

Далее нам остаётся только распределить шарики по колбам. Итоговый class ImageParser доступен на github.

Преобразуем программу в Telegram Bot

Узнать про то, как сделать телеграм бота на Python можно сразу из нескольких статей на хабре. Я лишь опишу пару нюансов, с которыми столкнулся.

Так как наш бот хоститься на Яндекс.Функции триггером к его запуску будет запрос на заданный нами webhook.

Whenever there is an update for the bot, we will send an HTTPS POST request to the specified url, containing a JSON-serializedUpdate.

Если в сообщении есть массив photo, то можно увеличить вероятность распознавания шариков выбрав фотографию с максимальным весом:

if photos := message.get('photo'):    # here photos is an array with same photo of different sizes    # get one with the highest resolution    hd_photo = max(photos, key=lambda x: x['file_size'])

Чтобы скачать картинку, придётся сделать 2 запроса к Telegram API

# Получение данных о файле, нас интересует ключ ответа file_pathGET https://api.telegram.org/bot{BOT_TOKEN}/getFile?file_id={file_id}# Получение самого файлаGET https://api.telegram.org/file/bot{BOT_TOKEN}/{file_path}

В остальном же всё просто получаем картинку, скармливаем её парсеру и затем алгоритму-решателю.

main.py
def handler(event: Optional[dict], context: Optional[dict]):    body = json.loads(event['body'])  # type: ignore    print(body)    message = body['message']    chat_id = message['chat']['id']    if photos := message.get('photo'):        # here photos is an array with same photo of different sizes        hd_photo = max(photos, key=lambda x: x['file_size'])  # get one with the highest resolution        try:            file = telegram_client.download_file(hd_photo['file_id'])        except TelegramClientError:            text = "Cant download the image from TG :("        else:            file_bytes = np.asarray(bytearray(file.read()), dtype=np.uint8)            try:                image_parser = ImageParser(file_bytes)                colors = image_parser.to_colors()            except ImageParserError as exp:                text = f"Cant parse image: {exp}"            else:                puzzle = BallSortPuzzle(colors)  # type: ignore                solved = puzzle.solve()                if solved:                    text = get_telegram_repr(puzzle)                else:                    text = "This lvl don't have a solution"    else:        return {            'statusCode': 200,            'headers': {'Content-Type': 'application/json'},            'body': '',            'isBase64Encoded': False,        }    msg = {        'method': 'sendMessage',        'chat_id': chat_id,        'text': text,        'parse_mode': 'Markdown',        'reply_to_message_id': message['message_id'],    }    return {        'statusCode': 200,        'headers': {'Content-Type': 'application/json'},        'body': json.dumps(msg, ensure_ascii=False),        'isBase64Encoded': False,    }

Отмечу ещё один нюанс: телеграм очень строго следует политике экранирования спецсимволов. Для Markdown это:

To escape characters '_', '*', '`', '[' outside of an entity, prepend the characters '\' before them.

Любой такой неэкранированный символ и вы не увидите ответа в телеграм-чате. И останется только гадать является ли это ошибка интеграции или вот такой коварный баг. Будьте осторожны.

Деплой бота в Яндекс.Функцию

Про создание Я.Функции также есть отличная статья от @mzaharov. Там подробно описан процесс заведения функции, а также установки вебхука для телеграмм бота.

Я расскажу как сделал Continuous Delivery при помощи GitHub Actions. Каждая сборка мастера увенчивается деплоем новой версии функции. Такой подход заставляет придерживаться модели разработки GithubFlow с его главным манифестом

Anything in themasterbranch is always deployable.

Каждая сборка мастера состоит из 3ёх этапов

  • lint (black, flake8, isort, mypy) проверка кода на соответствие всем стандартам Python 2020

  • test тестируем программу с помощью pytest, поддерживая качество и покрытие кода

  • deploy непосредственно заливаем новую версию приложения в облако

Деплоить будем с помощью Yandex-Serveless-Action уже готового Action для использования в своих пайплайнах

  deploy:    name: deploy    needs: pytest    runs-on: ubuntu-latest    if: github.ref == 'refs/heads/master'    steps:      - uses: actions/checkout@master      - uses: goodsmileduck/yandex-serverless-action@v1        with:          token: ${{ secrets.YC_TOKEN }}          function_id: ${{ secrets.YC_FUNCTION_ID }}          runtime: 'python38'          memory: '256'          execution_timeout: "120"          entrypoint: 'main.handler'          environment: "\            TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}"          source: 'app'

Переменные окружения программы и сборки спрячем в GitHub Secrets на уровне репозитория.

Результат

Пример работы @ballsortpuzzlebotПример работы @ballsortpuzzlebot

Бота можно найти в telegram по позывному @ballsortpuzzlebot.

Все исходники на Github.

Присоединяйтесь к маленькому community любителей этой игры в telegram. Бот был добавлен в группу и внимательно следит за всеми отправленными картинками.

Бонус! Уровни, у которых нет решения
Lvl 4091Lvl 4091Lvl 6071Lvl 6071

Мой алгоритм умывает руки говорит что перебрал все возможные комбинации и решения нет. Возможно это баг алгоритма.. или QA-отдел мобильной игры просто забил на эти уровни, так как не предполагал, что кто-то так далеко зайдёт)

Заключение

Для меня это был интересный опыт скрещивания технологий (Telegram API + Python + OpenCV + Lambda). Надеюсь он окажется полезен кому-нибудь ещё.

Найденные баги, предложения по оптимизации алгоритма, или даже PR в репозиторий крайне приветствуются

С новым годом!

Подробнее..

Ищем максимальную разницу между соседями. User-friendly-разбор задачи по алгоритмам

08.12.2020 12:05:45 | Автор: admin
Привет, Хабр!

Давайте поговорим про алгоритмы. Новички часто воспринимают их как что-то тяжёлое, сложное и непонятное, и отчасти это правда, но алгоритмы это базис. А чем лучше вы знаете базис своей специальности, тем с большей вероятностью преуспеете в ней.



Сегодня мы посмотрим на одну красивую задачу по алгоритмам. Не будем отпугивать людей от работы с алгоритмами на самом старте, поэтому в нашем разборе не будет ни развесистых деревьев отрезков, ни разномастных оптимизаций задачи о рюкзаке, ни вероятностных тестов на простоту. User-friendly algos.

Вот задача: найти максимальную разницу между соседями.

Дан массив из N целых чисел. Он никак не упорядочен, а числа могут повторяться. Предположим, что мы отсортировали его и вычислили разницу между каждой парой последовательных элементов. Необходимо найти максимальную такую разницу и сделать это наиболее оптимальным способом.

Сложно? Можете попробовать сделать это до того, как нажмёте Читать дальше, а потом проверим решение.

Итак, поехали. Максимальная разница между соседями.

Рассмотрим пример:
Пусть дан массив из N=11 элементов.
A = [-1, -3, 10, 20, 21, 4, 3, 22, 10, -2, 15]

Cначала решим задачу наивно, это часто помогает. Мы можем сделать именно то, что от нас требует условие задачи отсортировать числа и найти разницу между соседними элементами. В зависимости от того, какую сортировку мы будем использовать, сложность решения будет варьироваться.

Если используем qsort или mergesort, то временная сложность составит O(N log N). Если нам известно, что числа распределены достаточно плотно на множестве целых чисел, то можно использовать сортировку подсчётом. Тогда мы получим O(U + N), где U разность между максимальным и минимальным элементом. Случай относительно редкий, но стоит помнить про такую возможность.

После сортировки получим массив:
As = [-3, -2, -1, 3, 4, 10, 10, 15, 20, 21, 22]
Выпишем массив разностей: D = [1, 1, 4, 1, 6, 0, 5, 5, 1, 1]
Получаем, что максимальная разница составляет 6.

Теперь подумаем, можно ли решить задачу быстрее? При переборе пар соседних элементов мы будем рассматривать много пар с маленькой разностью. Такие пары, возможно, и не смогут никогда дать наибольшую разность. И действительно, в отсортированном массиве у нас есть N-1 пара соседних чисел, разность между максимумом и минимумом пусть равна U. Тогда у наибольшей разности значение должно быть хотя бы U / (N 1). Эта оценка верна по принципу Дирихле.

Если голуби рассажены в клетки, причём число голубей больше числа клеток, то хотя бы в одной из клеток находится более одного голубя.


9 клеток содержат 10 голубей, по принципу Дирихле, хотя бы в одной клетке находятся более одного голубя (Википедия)

Пусть D[1] = As[2] As[1], D[n 1] = As[n] As[n 1], As[i] i-й элемент отсортированного массива. Тогда D[i] >= 0, D[1] + + D[N-1] = U.

Если предположить, что максимум из всех D[i] меньше U/(N-1), то вся сумма D[1] + + D[N-1] будет строго меньше U противоречие.

Отлично, мы получили нижнюю оценку на максимальную разность! Теперь попробуем как-то локализовать слишком близкие друг к другу элементы разобьём отрезок от минимального до максимального элемента на полуинтервалы длиной L=U/(N-1). Каждый элемент попадёт ровно в один полуинтервал. Получим разбиение всех элементов на непересекающиеся группы, их ещё называют батчами.

Определить, в какой именно из них попал элемент x, очень просто надо вычислить 1 + int((x a_min) / L) (нумеруем с единицы), где a_min минимальный элемент в массиве A, его можно найти за один проход по исходному массиву. Исключение составит только максимальный элемент элементы с таким значением лучше сделать отдельным батчом.

На рисунке представлено распределение из описанного выше примера. Для ясности проделаем эту процедуру руками:

U = 22 (-3) = 25, N = 11 => L = 25/(11-1) = 2.5
A = [-1, -3, 10, 20, 21, 4, 3, 22, 10, -2, 15]
(-1 (-3)) / 2.5 = 0.8 => batch = 1
(-3 (-3)) / 2.5 = 0 => batch = 1
(10 (-3)) / 2.5 = 13/2.5 = 5.2 =>batch = 6
(20 (-3)) / 2.5 = 23/2.5 = 9.2 => batch = 10
(21 (-3)) / 2.5 = 24/2.5 = 9.6 => batch = 10
(4 (-3)) / 2.5 = 7/2.5 = 2.8 => batch = 3
(3 (-3)) / 2.5 = 6/2.5 = 2.4 => batch = 3
(22 (-3)) / 2.5 = 10 => batch = 11
(10 (-3)) / 2.5 = 5.2 => batch = 6
(-2 (-3)) / 2.5 = 0.4 => batch = 1
(15 (-3)) / 2.5 = 7.2 => batch = 8



Распределение по батчам выполняется за линейное время и требует O(n) дополнительной памяти. Обратите внимание, что элементы из одного батча не могут дать максимальную разницу между двумя элементами. Мы специально подобрали его размер таким образом.

Где могут находиться два соседних по значениям элемента? Конечно же, в соседних непустых батчах! Например, на рисунке элементы из третьего и шестого батча могут идти подряд в отсортированном массиве, так как батчи между ними пустые. Понятно, что соседними будут только два элемента максимум из 3-го батча и минимум из 6-го. Аналогично, кандидатами на пару с максимальной разностью будут максимальный элемент первого батча и минимальный элемент третьего.

Выпишем все возможные пары элементов, которые могут дать наибольшую разность. Обозначим как min(i) минимальный элемент в i-й группе, как max(i) максимальный элемент.

max(1) = -1 min(3) = 3
max(3) = 4 min(6) = 10
max(6) = 10 min(8) = 15
max(8) = 15 min(10) = 20
max(10) = 21 min(11) = 22

Мы определили пары, которые стоит рассматривать. На практике необходимо сделать один проход по всем батчам, поддерживать последний непустой батч и максимальное значение в нём. Когда мы наткнёмся на следующий непустой батч, то найдём в нём минимум и попробуем обновить ответ.

Порадуемся мы решили задачу за время O(N).

На самом деле, мы использовали достаточно известную идею и по сути проделали первые шаги карманной сортировки, в оригинале её называют bucket-sort.


Элементы раскладываются по корзинам, а потом элементы в каждой корзине сортируются


В худшем случае она работает за O(n^2), но среднее время работы при достаточном количестве батчей и равномерном распределении элементов составляет O(n).

Но постойте, а как же случай, когда U=0, почему вы не рассмотрели его?, спросит внимательный читатель. Этот случай вырожденный, вот мы его и не рассмотрели, но давайте сделаем это сейчас для полноты вариантов.

Если разница между минимумом и максимумом равно нулю, то максимальная разница тоже равна нулю. Собственно, это всё.
Подробнее..

Root Cause Analysis как метод предотвращения багов

02.03.2021 22:15:14 | Автор: admin

Привет! Мое имя Юра Гомон, яBATech Lead вNIX ивот уже 8лет занимаюсь бизнес-анализом, помогая реализовывать веб- имобильные решения для бизнеса, атакже автоматизировать бизнес-процессы. Мое имя кажется Вам знакомым, т.к. недавно я опубликовал на Хабре свой BADigest.

Цель статьи напомнить бизнес-аналитикам ометоде Root Cause Analysis (далее RCA) иподелиться опытом его применения для предотвращения багов.

В сети очень много информации по RCA, потому не хочу повторяться в отношении теории. Если Вы еще не использовали Problem-solving техники и, в частности, RCA, рекомендую ознакомиться с материалами по ссылками ниже, а уже затем переходить к статье:

В своей статье, планирую поделиться кейсами из практики, где RCA очень помогло не делать ошибок в будущем либо предотвратить потенциальные последствия.

Кейс первый ополитиках

Вливной enterprise-системе на200+ пользователей все было хорошо: жили нетужили, работали три года после релиза. Новодин прекрасный день при попытке перейти поссылке всистему вместо желаемого результата пользователей ждало угрожающее сообщение

Аналог сообщения, которое увидели пользователи. Оригинал несохранился :) Источник:Bytebitebit

Разумеется, это вызвало, мягко говоря, недоумение. Вглазах пользователей работа оказалась заблокированной. Мало того, что угроза кражи паролей, так еще ипредложение продолжить, ноуже unsafe. Страшно.

Как только опроблеме стало известно, ответственные как могли успокоили пользователей ипообещали быстро все выяснить ипоправить. Первым делом, они помчались разбираться вместную поддержку, где состоялся примерно такой диалог:

Почему пользователи видят эту страницу?
Посмотрим А-а, так это заэкспайрился SSL-сертификат, продлим, ивсе будет впорядке.
Апочему это несделали заранее?
Никто непоставил напоминание, атри года быстро пролетели.
Резонно. Как думаете, почему непоставили?
Хм, по-моему, это первый проект, где была потребность виспользовании SSL. Это уже после вас стали сертификаты покупать идругим.
Так это ивдругих системах может случиться?
Пожалуй, пойдем везде проверим, поставим напоминалки ивгайдлайны внутренних систем добавим, что надо незабывать это делать.

Витоге ошибку быстро исправили иработа возобновилась. Пользователей обняли, пообещали новых вкусных фичи, отдышавшись, пошли делать обещанное.

Что изэтого стоит вынести? Ответственные, те, кто знаком сметодами RCA иFive Whys, нетолько исправили ошибку, ноначали задавать вопросы, чтобы найти истинную причину. Как только это случилось, проблему превентивно закрыли везде, где она могла произойти, тоесть предотвратили баг безопасности вдругих системах. Косвенно это привело ктому, что вкомпании стали уделять еще больше внимания безопасности.

Кейс второй олюдях

Мой приятель, ресурсный менеджер изодной украинской компании, поделился сомной одним любопытным случаем. Подбивая статистику вочередном проекте, онобратил внимание, что количество багов стало увеличиваться. Менеджер обратился ктехлиду задеталями иобнаружил следующее: большинство ошибок сделал один итотже Junior-разработчик. Это оказалось неслучайностью, атенденцией впоследние несколько итераций. Специалист стал пропускать требования, вкоде появились детские ошибки. При этом ранее небыло никаких нареканий.

Менеджер вызвал человека наразговор, новсилу замкнутости последнего это непомогло докопаться досути. Максимум, что узнал нет настроения работать, сильно устаю. Чтобы все-таки разобраться сэтом ситуацией, менеджер использовал технику RCA, ивот что получилось:

  1. Менеджер задокументировал ключевые поинты разговора счеловеком названные импричины.

  2. Предположил варианты сосвоего опыта.

  3. Собрал доказательства существованию причин, проанализировал.

  4. Попытался связать разные причины водну систему, поскольку редко бывает так, чтобы причина была только вчем-то одном.

Граф причин проблемы, воссозданный сослов рассказчика

Большинство причин нижнего уровня можно было решить быстро, анекоторые догадки (овертаймы, нежелание ментора менторить) ивовсе неподтвердились. Однако глаз менеджера зацепился загипотезы, выделенные красным, так как ихнеполучилось проработать из-за скрытности человека инедостатка информации наповерхности. Менеджер продолжил изучать причины, после чего выстроил следующий таймлайн событий:

  1. Сначала человек стал засиживаться вофисе после работы почти каждый день.

  2. Затем примерно водно итоже время стал слишком часто залипать винстаграм ифейсбук, что было хорошо видно наего мониторе, атакже много выходить поговорить потелефону.

  3. Следом вместо домашней еды онстал питаться подножным кормом.

Менеджер проанализировал улики расследования ивызвал человека напрямой разговор после работы, где уже благодаря паре бокалов виски узнал, что успециалиста дома проблемы, все идет красставанию сдевушкой, аработать втаких условиях онэффективно неможет. Узнав это, менеджер предложил сотруднику отгул нанесколько дней, чтобы тот имел возможность осмыслить проблему вспокойном рижиме ипридумать, что делать дальше. После этого разработчик вернулся уже чуть более спокойным иположение немного наладилось.

Втечение следующих пары месяцев разработчик брал отгулы еще несколько раз, аменеджер оказывал моральную поддержку. Несоветом, нопомогал переключаться начто-то позитивное настолки, совместные обеды итак далее. Витоге человек получил время ивнутренний ресурс для решения проблем дома, азатем вернулся всвой нормальный рабочий ритм, иболее кнему вопросов невозникало.

Кейс третий оработе стребованиями

Когда БАвходит впроект, который идет уже некоторое время, первая задача разобраться стребованиями, тоесть провести аудит. Обэтом кейсе яделалдоклад наNIX Multiconfв2019году. Сегодня раскрою некоторые детали того случая сточки зрения применения RCA: как искали причины того, почему впроекте начало появляться много багов.

Впервыйже день язапросил доступ кканалам коммуникации, описанию стейкхолдеров, команд ипроцессов, покоторым жил проект, так как поопыту это места, где стоит искать корни проблемы впервую очередь. Втечение недели яполучил нужную информацию, категоризировал ееипроработал проблемы напару уровней вниз, используя Five Whys. Ниже представлен отрывок изперечня проблем, которые удалось выявить:

Коммуникация

Невозможно было найти ответ или решение через время. Обсуждение требований велось вAsana, Slack, JIRA, через почту, вонлайн- иофлайн-документах. Среди всех источников небыло единого:

  • клиент писал там, где ему было удобно втекущий момент;

  • остальные команды неподдержали нашу критику, поскольку имитак норм;

  • бюджет наведение SRS, митинг-минуток иструктурирование информации невыделялся.

Наша команда неполучала ответов вовремя. Другие команды непонимали наших вопросов:

  • трудности перевода;

  • если непинговать, томогли забы(и)вать нам ответить;

  • пинговать из-за большой разницы сдвумя другими таймзонами было затруднительно.

Стейкхолдеры

Наша команда получала разную информацию изразных источников.

Небыло обмена решениями сдругими командами:

  • небыло четких зон ответственности вплане шаринга информации;

  • структурированные минутки исинки команд требовали времени, которое нехотели выделять.

Клиент часто менял требования/ принимал решения. Настолько часто, что одна команда успевала получить одно, авторая уже другое:

  • уклиента небыло четкой бизнес-идеи, онстарался быстро адаптироваться под нужды целевой аудитории;

  • клиент хотел реализовать как можно больше фич, невзирая наобщий концепт приложения, атакже удовлетворить всех пользователей;

  • назаказчика давили инвесторы, которые требовали постоянного увеличения аудитории ифинансовых результатов.

Уже этих причин вполне достаточно, чтобы понять масштаб трагедии, источник потенциальных дефектов. Безусловно, снашей стороны также были проблемы. Например, техлид выполнял рольБА ипытался кое-как зафиксировать требования врамках своей загрузки, азначит, недостаточно ревьювил более молодых разработчиков. Однако больше всего проблем было из-за хаоса вкоммуникации, отсутствия продуктовой стратегии, нефиксирования требований вдостаточном объеме. Все это привело кневозможности планировать архитектуру иписать код требуемого качества.

Недожидаясь правок понескольким десяткам других пунктов, ЛПРы состороны команды реорганизовали перечень каналов связи иоставили всего три: обсуждение идей велось вAsana, дизайна итребований всоответствующих каналах Slack, тамже создали канал для общих вопросов. Кроме того, все решения покаждой функции стали фиксировать вJIRA, куда перевели только тетаски, которые должны были идти вразработку. Вдополнение ввели жесткие правила понеизменности скоупа врамках спринта (либо полной приостановке ипланированию спринта снуля) идлительности спринта.

Витоге чудесным образом количество дефектов стало резко уменьшаться. ЛПРы воодушевились успехом ипродолжили вносить изменения, которые также несли положительный результат. Но, как оказалось, положительным оноказался только для нас, т.к. вскоре клиент попрощался снами под предлогом того, что небыл готов ктакому уровню бюрократии :) Если заинтриговал, товдокладе поссылке выше больше подробностей.

Ичто?

Есть вещи, которые вот уже 8лет, смомента как явошел вIT, янеперестаю евангелизировать, поскольку нахожу имприменение либо вижу отражение вокружающем мире. Среди них философияпиши-сокращай, модель Кано, принцип Парето, think out ofthe box итак далее. Как выуже догадались, RCA входит вэтот перечень. Авсе потому, что важнейшая, если непервичная задачаБА заключается втом, чтобы понять почему. Будь топроблемная функция всистеме, ошибки вбизнес-процессах, нюансы человеческого поведения, негативные события вокруг все имеет глубокие корни.

Нередко вопросы почему спасают продукт отфичи, которая абсолютно ему ненужна, отпринятия решения, которое приведет кнегативным последствиям либо вызовет баг всистеме. Аесли неспасет, тохотябы поможет подготовиться квозможным трудностям исделать так, чтобы больше ненаступать натеже грабли.

RCA, конечноже, несеребряная пуля, ноинструмент, который однажды сможет предотвратить очередную проблему. Многие изнас пользуются RCA интуитивно, поскольку любознательность присуща людям, работающим вIT. Однако, использование техники наполную выведет вас нановый уровень при поиске причин ошибок.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru