Как стать автором
Обновить

Как аналитику научиться читать код без навыков программирования

Уровень сложностиСредний
Время на прочтение13 мин
Количество просмотров10K

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

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

Я верю в то, что если ты работаешь в 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-канале: Записки системного аналитика

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Возникала ли у вас необходимость в повседневной работе разбираться в коде сервисов?
45.45% Я СА/БА — да20
15.91% Я СА/БА — нет7
38.64% Я просто мимо крокодил (посмотреть результаты)17
Проголосовали 44 пользователя. Воздержались 4 пользователя.
Теги:
Хабы:
Всего голосов 7: ↑6 и ↓1+8
Комментарии16

Публикации

Истории

Работа

Ближайшие события

4 – 5 апреля
Геймтон «DatsCity»
Онлайн
8 апреля
Конференция TEAMLY WORK MANAGEMENT 2025
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань
20 – 22 июня
Летняя айти-тусовка Summer Merge
Ульяновская область