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

А зачем аналитикам читать код?

Я верю в то, что если ты работаешь в IT в технической команде, то ты - инженер.
А если уж ты инженер, то ты должен уметь не только в бизнес и анализ, но и в техническую сторону вопроса.

Проще говоря, умение читать код (а равно понимать то, как там все устроено "под капотом", какие есть паттерны проектирования и т.д.) сильно поможет при:

  • проектировании архитектуры;

  • разборе инцидентов с продакшена;

  • погружении в новый домен или рефакторинге легаси-систем.

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

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

Способ 1

Не благодарите
Не благодарите

Отучитесь на курсах программирования. Это будет не быстро, но вас будет мотивировать потраченная на это куча денег. А может и не будет.

Субъективно, проще всего начинать с того, что на хайпе. Интересно будет вернуться сюда и прочитать это лет так через 15, но сегодня я бы безальтернативно выбрал Python в качестве первого языка. Для быстрого освоения базовых принципов - самое то.

Как альтернатива платным курсам существует масса бесплатных материалов в интернете, которые ничем не уступают платным по качеству.

Второе субъективное ощущение - проще сразу учиться на практических кейсах. Как раз то, чего полно в интернете и бесплатно. Решение вороха задач на сортировку массива двадцатью пятью методами это замечательно, но не мотивирует ввиду отсутствия наглядного результата. А «базу» вы потом всегда успеете подтянуть, главное, чтобы в вас разгорелся интерес и желание развиваться «вглубь» технического материала (именно так и случилось у меня в свое время, я вообще первым делом полез изучать ML на Python, не разбираясь даже в синтаксисе языка, благо, что курс вышмата еще помнил на тот момент).

Способ 2

LLM. Копируете фрагмент кода, закидываете в нейронку с вопросом "Расскажи подробно, что выполняет этот фрагмент кода на <...подставьте сюда язык программирования...>", получаете расписанный по строчкам ответ.

Способ 3

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

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

Основы синтаксиса на примере Python
  1. Основы синтаксиса

    • Комментарии: Как комментировать код?

      # Это однострочный комментарий
      """ Это многострочный комментарий """
    • Переменные: Как объявлять переменные и присваивать им значения?

      x = 10  # целое число
      y = 3.14  # вещественное число
      name = "Python"  # строка
      is_true = True  # булево значение
    • Типы данных: Основные типы данных (целые числа, вещественные числа, строки, списки, словари, множества и т.д.)

      my_list = [1, 2, 3, 4, 5]  # список
      my_dict = {'key1': 'value1',
                 'key2': 'value2'}  # словарь
      my_set = {1, 2, 3}  # множество
    • Операторы: Арифметические операторы, операторы сравнения, логические операторы.

      a = 5 + 3  # сложение
      b = 8 - 2  # вычитание
      c = 2 * 3  # умножение
      d = 9 // 2  # целочисленное деление
      e = 9 % 2  # остаток от деления
      
      f = a > b  # сравнение (больше)
      g = a < b  # меньше
      h = a >= b  # больше или равно
      i = a <= b  # меньше или равно
      j = a == b  # равенство
      k = a != b  # неравенство
      
      l = (a > b) and (c < d)  # логическое И
      m = (a > b) or (c < d)  # логическое ИЛИ
      n = not (a > b)  # отрицание
  2. Управляющие конструкции

    • Условия: Условные операторы ifelifelse.

      age = 18
      if age < 18:
          print("Вы несовершеннолетний.")
      elif age == 18:
          print("Вам ровно 18 лет.")
      else:
          print("Вы совершеннолетний.")
    • Циклы: Цикл for и цикл while.

      # Цикл for
      fruits = ["яблоко", "апельсин", "банан"]
      for fruit in fruits:
          print(fruit) # яблоко, апельсин, банан
      
      # Цикл while
      count = 0
      while count < 5:
          print(count) # 0, 1, 2, 3, 4
          count += 1
  3. Функции

    • Определение функций, передача параметров, возвращение значений.

      def greet(name):
          print(f"Привет, {name}!")
      
      greet("Мир")
      
      def add(a, b):
          return a + b
      
      result = add(2, 3)
      print(result)  # Выведет 5
  4. Модули и пакеты

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

      import math
      
      print(math.pi)  # Выведет значение числа π
      
      from datetime import datetime
      
      now = datetime.now()
      print(now)  # Выведет текущую дату и время
  5. Работа с файлами

    • Открытие, чтение, запись и закрытие файлов.

      with open('example.txt', 'w') as file:
          file.write("Привет, мир!\n")
      
      with open('example.txt', 'r') as file:
          content = file.read()
          print(content)
  6. Исключения

    • Обработка исключительных ситуаций с помощью блоков tryexceptfinally.

      try:
          x = int(input("Введите число: "))
          y = 1 / x
      except ZeroDivisionError:
          print("Ошибка: Деление на ноль.")
      except ValueError:
          print("Ошибка: Недопустимое значение.")
      finally:
          print("Завершение программы.")

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

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

Разработка идет "от объекта" - представим, что необходимо спроектировать каталог продуктов. Сначала необходимо будет описать объекты каталога - продукты, их атрибуты (название, свойства и т.д.), а затем методы объектов - то, что можно делать с продуктами (создавать, изменять, каким-либо иным образом взаимодействовать с ними).

Понятия классов и методов (ООП)

Классы и методы являются основными элементами объектно-ориентированного программирования (ООП). Классы позволяют определять новые типы данных, объединяя данные и функции, работающие с ними, в единое целое. Методы — это функции, связанные с конкретными классами и работающими с их данными.

  1. Определение объекта Объект — это сущность с конкретными характеристиками и функциями

  2. Определение класса Класс — это шаблон для создания объектов. В Python класс определяется с помощью ключевого слова class.

    class Dog:
        pass

    Здесь определен класс Dog, который пока ничего не делает. Ключевое слово pass используется, чтобы указать, что тело класса пустое.

  3. Атрибуты класса Атрибуты класса — это переменные, которые принадлежат классу и доступны всем объектам этого класса. При создании объекта класса, они заполняются конкретными значениями

    class Dog:
        species = "Canis familiaris"  # атрибут класса

    Теперь у класса Dog есть атрибут species, который доступен всем объектам этого класса.

  4. Методы класса Методы — это функции, определенные внутри класса. Они работают с данными объекта или класса.

    class Dog:
        def bark(self):  # метод класса
            print("Гав!")

    Метод bark — это функция, определенная внутри класса Dog. Обратите внимание на аргумент self, который ссылается на текущий объект.

  5. Конструктор Конструктор — специальный метод, вызываемый при создании объекта класса. В Python конструктор называется __init__.

    class Dog:
        def __init__(self, name, age):  # конструктор
            self.name = name  # атрибут объекта
            self.age = age  # атрибут объекта

    Теперь при создании объекта класса Dog необходимо передать ему имя и возраст.

  6. Создание объектов Чтобы использовать класс, нужно создать объект (или экземпляр) этого класса.

    scooby = Dog("Скуби", 3)  # создание объекта
    rex = Dog("Рекс", 5)  # еще один объект

    Оба объекта имеют свои собственные значения атрибутов name и age.

  7. Доступ к атрибутам и методам Доступ к атрибутам и методам объекта осуществляется через точку.

    print(scooby.name)  # выведет "Скуби"
    rex.bark()  # выведет "Гав!"
  8. Наследование Наследование позволяет одному классу унаследовать свойства другого класса.

    class Poodle(Dog):  # Poodle наследует от Dog
        def speak_french(self):
            print("Oui Oui!")

    Класс Poodle теперь обладает всеми методами и атрибутами класса Dog, а также своим собственным методом speak_french.

  9. Полиморфизм Полиморфизм позволяет объектам разных классов реагировать на одни и те же сообщения по-разному.

    class Cat:
        def meow(self):
            print("Мяу!")

    Теперь у нас есть два класса, Dog и Cat, оба с методами, которые делают разные вещи, хотя называются одинаково.

Пример реализации парадигмы ООП:

class Animal:
    def __init__(self, name, sound):
        self._name = name  # Инкапсуляция: защищённый атрибут
        self._sound = sound

    def make_sound(self):
        print(f"{self._name} говорит {self._sound}")

    def sleep(self):
        print(f"{self._name} спит...")

class Dog(Animal):
    def __init__(self, name):
        super().__init__(name, "Гав!")  # Наследование

    def wag_tail(self):
        print(f"{self._name} виляет хвостом...")

class Cat(Animal):
    def __init__(self, name):
        super().__init__(name, "Мяу!")  # Наследование

    def purr(self):
        print(f"{self._name} мурлычет...")

def animal_action(animal):
    animal.make_sound()  # Полиморфизм
    animal.sleep()

doggy = Dog("Скуби-Ду")
catze = Cat("Гарфилд")

animal_action(doggy)  # Скуби-Ду говорит Гав!, Скуби-Ду спит...
animal_action(catze)  # Гарфилд говорит Мяу!, Гарфилд спит...

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

Рассмотрим архитектуру простого приложения из двух сервисов из базы данных.

Простой пример
Простой пример

Видим, что у нас есть внешний сервис, с которого можно отправить запрос GET /api/products в сервис Main для получения списка товаров из таблицы Product базы данных main.

В свою очередь сервис Main при поступлении такого запроса ищет необходимые товары в своей БД, затем направляет запрос POST /api/products в сервис Admin, для, например, дальнейшей обработки записи о том, список каких товаров запрашивал пользователь, и возвращает ответ.

Про REST API, интеграции и базы данных вы можете подробнее прочитать в других статьях.

Точкой входа в исполнение программы, как правило, является файл main.py, если иное не переопределено.

Открываем main.py и видим много непонятных цветных букв.

#--------------------------------------------------------------
# Импортируются основные библиотеки для работы с Flask (Flask),
# управление кросс-доменными запросами (CORS),
# работа с миграциями баз данных (Migrate)
# и объект db (экземпляр SQLAlchemy).
from flask import Flask
from flask_cors import CORS
from flask_migrate import Migrate
from database import db
#--------------------------------------------------------------
# Строка подключения указывает на базу данных PostgreSQL,
# которая находится по адресу host.docker.internal:5434, имя пользователя – root,
# пароль – root, а база данных называется main.
db_uri = "postgresql+psycopg2://root:root@host.docker.internal:5434/main"
#--------------------------------------------------------------
# Создается объект Migrate,
# который будет использоваться для управления миграцией схемы базы данных.
migrate = Migrate()
#--------------------------------------------------------------
# Функция create_app():  и настраивает его:
def create_app():
    # Cоздает экземпляр приложения Flask
    app = Flask(__name__)
    # Устанавливает конфигурацию соединения с базой данных (SQLALCHEMY_DATABASE_URI).
    app.config["SQLALCHEMY_DATABASE_URI"] = db_uri
    # Включает поддержку кросс-доменных запросов через CORS(app).
    CORS(app)
    # Инициализирует объекты db и migrate для использования в приложении.
    db.init_app(app)
    migrate.init_app(app, db)
    # Импортирует и регистрирует эндпоинты из кастомного модуля routes.
    from routes import register_routes
    register_routes(app, db)
    # Возвращает результатом выполнения функции экземпляр приложения
    return app
#--------------------------------------------------------------
# Создается экземпляр приложения 
app = create_app()
#--------------------------------------------------------------
# Запускается приложение (app) на сервере с включенным режимом отладки (debug=True),
# который слушает все интерфейсы с адресом (host="0.0.0.0") на порту 5000.
if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=5000)

Вспоминаем, что нас интересует работа сервиса, а значит - все возможные взаимодействия (которые в данном кейсе идут по HTTP), следовательно ищем паттерны, указывающие на что-нибудь, похожее на URL-адрес.

С помощью поиска Ctrl+Shift+F открываем окно поиска и ищем паттерны, соответствующие именам эндпоинтов или (для нашего кейса) делаем Ctrl+LeftClick по функции register_routes(app, db ) и видим следующее содержимое:

#--------------------------------------------------------------
# Импортируется функция jsonify из Flask для преобразования данных в JSON-формат,
# библиотека requests для выполнения HTTP-запросов
# и модель Product из модуля models.
from flask import jsonify
import requests
from models import Product
#--------------------------------------------------------------
# Эта функция принимает два аргумента: app (экземпляр приложения Flask)
# и db (объект базы данных).
# Функция предназначена для регистрации маршрутов в приложении.
def register_routes(app, db):
#--------------------------------------------------------------
    # Декоратор @app.route определяет новый маршрут для URL /api/products,
    # который обрабатывает только запросы методом POST.
    @app.route("/api/products", methods=['GET'])
    #--------------------------------------------------------------
    def get_products(request):
        # Сначала выполняется запрос к базе данных для получения всех записей
        # из таблицы Product. Метод query.all() возвращает список объектов Product.
        products = Product.query.all()
        #--------------------------------------------------------------
        try:
          # Затем отправляется POST-запрос в сервис Admin
          # по адресу http://host.docker.internal:8000/api/products.
          # В теле запроса передается информация о найденных товарах в формате JSON.
          response = requests.post('http://host.docker.internal:8000/api/products', json={
              'products_from_database': products
          })
          #--------------------------------------------------------------
          # После отправки запроса проверяется статус ответа.
          # Если запрос завершился неудачно (т.е., если response.ok равно False),
          # то выводится сообщение об ошибке вместе с текстом ответа.
          if not response.ok:
              print(f'Ошибка отправки запроса в сервис Admin: {response.text}')
          #--------------------------------------------------------------
        # Если во время отправки запроса возникает исключение,
        # оно перехватывается блоком except,
        # и выводится соответствующее сообщение об ошибке.
        except Exception as e:
            print(f'Ошибка отправки запроса в сервис Admin: {e}')
        #--------------------------------------------------------------
        # Независимо от успеха или неудачи отправки запроса в сервис Admin,
        # функция возвращает список товаров в формате JSON.
        return jsonify(products)

Первое, что встречаем, это запись @app.route("/api/products", methods=['GET']) - это и есть REST-эндпоинт, на который придет запрос с методом GET.

Под записью находится функция get_products(), принимающая объект запроса (request), и определяемая с помощью ключевого слова def.
В нашем случае тело функции содержит выполнение некоей бизнес-логики: поиск продуктов в базе данных в таблице Product, отправку POST-запроса о результатах поиска в сервис Admin на URL-адрес 'http://host.docker.internal:8000/api/products'и возврат ответа сервису Main.

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

Видим, что в ответе возвращаем переданную внутрь метода jsonify()переменную products, которая содержит в себе результат выполнения последовательности методов у объекта Product. Думаю, что не надо обладать сверхъестественными когнитивными способностями, чтобы понять, что сама по себе запись Product.query.all() намекает, что из таблицыProduct мы что-то запрашиваем (query), в данном случае - все (all()).

Теперь мы знаем, что первым делом обратились к таблице Product и запросили из нее все записи.

Делаем Ctrl+LeftClick на Product, попадаем сюда.

from dataclasses import dataclass
from database import db
# Объявляется класс Product, который является моделью для таблицы в базе данных,
# используя SQLAlchemy и Data Classes
@dataclass
#--------------------------------------------------------------
# Класс Product наследуется от db.Model, что делает его моделью SQLAlchemy.
# Это позволяет SQLAlchemy управлять таблицами и колонками в базе данных.
class Product(db.Model):
#--------------------------------------------------------------
    # Аннотации типов для полей класса. Они определяют типы данных для атрибутов.
    # Однако эти строки сами по себе не создают реальные атрибуты класса;
    # они просто указывают тип данных, ожидаемый при создании экземпляра класса.
    id: int
    title: str
    description: str
    #--------------------------------------------------------------
    # Столбцы базы данных
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200))
    description = db.Column(db.String(200))

Видим запись class Product(db.Model):- таким образом мы создали класс "Продукт" с атрибутами, перечисленными ниже (id, title, image). Запись типа id: int или title: str
называется аннотация типов (указывает на тип данных атрибута - числовой, строковый и т.д.).

С помощью декоратора@dataclass автоматизируется создание классов, используемых для хранения данных (проще говоря - можно меньше писать кода).
А с помощью передачи класса db.Model в параметры класса Product (реализуется наследование с точки зрения ООП), мы автоматизируем создание таблицы с продуктами в базе данных.

Затем определяются столбцы этой таблицы.
Запись id = db.Column(db.Integer, primary_key=True) - создается столбец id, типа Integer, значения которого являются первичным ключом в таблице.
Запись title = db.Column(db.String(200)) - создается столбец title, типа String, с ограничением длины содержимого в 200 символов. Столбец description создается аналогично.

Таким образом, мы понимаем, что у нас в БД есть таблица Product с тремя полями: id, title, description.

Возвращаясь к полученным из БД данным, мы видим, что вслед за этим происходит вызов requests.post('http://host.docker.internal:8000/api/products', json={ 'products_from_database': products }) - это и есть REST-клиент, который отправит запрос с методом POST в сервис Admin, который уже каким-то образом эти данные использует в своей бизнес-логике.

Ну и вспоминаем, что в конце у нас стоит ключевое слово return, возвращающее приложению, изначально вызвавшему сервис Main, ответ, так как у нас реализовано синхронное взаимодействие (запрос-ответ).

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

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

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

  • Изучить диаграммы (если они имеются), чтобы визуализировать потоки данных и взаимодействие с другими сервисами.

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

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

  • Изучить контроллеры, чтобы понять, какие методы доступны и какие данные они принимают и возвращают. Именно тут мы ищем паттерны, указывающие на что-нибудь, похожее на URL-адрес.

  • Изучить клиенты (адаптеры), чтобы понять, в какие внешние сервисы отправляются запросы, методы этих запросов, а также какие данные они отправляют и принимают в ответ.

  • Изучить репозитории, чтобы понять, в какие таблицы БД (при наличии) есть обращения, структуру этих таблиц и логику запросов в них.

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


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

Эту и другие статьи по системному анализу и IT-архитектуре, вы сможете найти в моем небольшом Telegram-канале: Записки системного аналитика

Комментарии (9)


  1. log76
    24.01.2025 05:51

    С такой логикой можно дойти до анализа какие команды отправляются процессору. Вот только зачем это системному аналитику? Если уже идёт разработка, то есть ТЗ на разработку функционала, есть документация на сервисы.


  1. nick_oldman Автор
    24.01.2025 05:51

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

    Я лично с таким не один раз сталкивался, и могу выделить отличную статью с Хабра, как восстанавливать документацию в таких случаях.


  1. Lewigh
    24.01.2025 05:51

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

    Ну купили вы курс по Python, прошли основы, даже изучили что такое ООП, пришли на проект где к примеру C#/Java/C++/PHP а еще неожиданно оказалось что то что пишут в книгах и то что Вы увидите - разные вещи. Ваши действия?

    По моему у вас просто подспудное желание разработкой заниматься и Вы зачем то себе придумали историю "зачем что аналитику это нужно".


    1. XelaVopelk
      24.01.2025 05:51

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


    1. nick_oldman Автор
      24.01.2025 05:51

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

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

      А с подспудным желанием вы угадали на 100% - я начинал свой путь как раз бэкенд-разработчиком :)


  1. Dissocial
    24.01.2025 05:51

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


  1. avorontsov
    24.01.2025 05:51

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


    1. nick_oldman Автор
      24.01.2025 05:51

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

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


    1. TerekhinSergey
      24.01.2025 05:51

      на мой испорченный взгляд разработчика с проф. деформацией не нужно никаких курсов. Важно уметь открывать проект в IDE и уметь в ней работать с точки зрения навигации по коду - искать вызовы функций и т.п. По названию функций обычно (не всегда) понятно примерно, что она делает, точку входа (где лежат контроллеры API например) поможет найти товарищ-разработчик, он же объяснит логику организации кода в проекте. Дальше уже код можно читать как книгу на плохом английском и понимать, как в целом он работает. Сложные места можно либо уточнять, либо пропускать, останавливаясь на предыдущем уровне "подробности".

      То есть весь гипотетический курс будет состоять из двух уроков: основы работы в IDE с точки зрения навигации по коду и логика организации проектов именно у вас в организации. Дальше уже можно начинать работать