Как аналитику научиться читать код без навыков программирования
Я, как системный аналитик, хочу чтобы начинающие специалисты могли самостоятельно разбираться в коде своих коллег, имели возможность самостоятельно находить ответы на свои вопросы и не отвлекали их от разработки.
А зачем аналитикам читать код?
Я верю в то, что если ты работаешь в IT в технической команде, то ты — инженер.
А если уж ты инженер, то ты должен уметь не только в бизнес и анализ, но и в техническую сторону вопроса.
Проще говоря, умение читать код (а равно понимать то, как там все устроено «под капотом», какие есть паттерны проектирования и т.д.) сильно поможет при:
проектировании архитектуры;
разборе инцидентов с продакшена;
погружении в новый домен или рефакторинге легаси-систем.
Всех, кто планирует заниматься системным анализом и проектированием архитектуры, убедительно прошу если не сразу, то впоследствии, изучить хотя бы один язык программирования и самому попробовать писать код. Это может быть любой, сколь угодно простой пет-проект или тренировка реализации архитектурных паттернов.
Все это поможет вам глубже понять архитектурные паттерны, иначе взглянуть на рабочие задачи, почувствовать себя на месте разработчика, понять возможные ограничения в реализациях, научиться самостоятельно оценивать сроки, да и просто получить жирный плюс в карму от команды :)
Способ 1
Не благодарите
Отучитесь на курсах программирования. Это будет не быстро, но вас будет мотивировать потраченная на это куча денег. А может и не будет.
Субъективно, проще всего начинать с того, что на хайпе. Интересно будет вернуться сюда и прочитать это лет так через 15, но сегодня я бы безальтернативно выбрал Python в качестве первого языка. Для быстрого освоения базовых принципов — самое то.
Как альтернатива платным курсам существует масса бесплатных материалов в интернете, которые ничем не уступают платным по качеству.
Второе субъективное ощущение — проще сразу учиться на практических кейсах. Как раз то, чего полно в интернете и бесплатно. Решение вороха задач на сортировку массива двадцатью пятью методами это замечательно, но не мотивирует ввиду отсутствия наглядного результата. А «базу» вы потом всегда успеете подтянуть, главное, чтобы в вас разгорелся интерес и желание развиваться «вглубь» технического материала (именно так и случилось у меня в свое время, я вообще первым делом полез изучать ML на Python, не разбираясь даже в синтаксисе языка, благо, что курс вышмата еще помнил на тот момент).
Способ 2
LLM. Копируете фрагмент кода, закидываете в нейронку с вопросом «Расскажи подробно, что выполняет этот фрагмент кода на <…подставьте сюда язык программирования…>», получаете расписанный по строчкам ответ.
Способ 3
Ну, а теперь я собственно изложу свое видение того, как можно быстро ознакомиться с базой любого языка.
Сначала желательно ознакомиться с базовым синтаксисом языка, а затем перейти к изучению парадигм программирования
Основы синтаксиса на примере Python
Основы синтаксиса
Комментарии: Как комментировать код?
# Это однострочный комментарий """ Это многострочный комментарий """
Переменные: Как объявлять переменные и присваивать им значения?
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) # отрицание
Управляющие конструкции
Условия: Условные операторы
if
,elif
,else
.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
Функции
Определение функций, передача параметров, возвращение значений.
def greet(name): print(f"Привет, {name}!") greet("Мир") def add(a, b): return a + b result = add(2, 3) print(result) # Выведет 5
Модули и пакеты
Импорт модулей и пакетов, использование стандартных библиотек.
import math print(math.pi) # Выведет значение числа π from datetime import datetime now = datetime.now() print(now) # Выведет текущую дату и время
Работа с файлами
Открытие, чтение, запись и закрытие файлов.
with open('example.txt', 'w') as file: file.write("Привет, мир!\n") with open('example.txt', 'r') as file: content = file.read() print(content)
Исключения
Обработка исключительных ситуаций с помощью блоков
try
,except
,finally
.try: x = int(input("Введите число: ")) y = 1 / x except ZeroDivisionError: print("Ошибка: Деление на ноль.") except ValueError: print("Ошибка: Недопустимое значение.") finally: print("Завершение программы.")
Объектно-ориентированное программирование (ООП) — методология или стиль программирования, при котором код организуется в логически связанные объекты.
Суть объектно-ориентированного программирования состоит в том, что все программы состоят из объектов. Каждый объект — является определённой сущностью со своими атрибутами и набором методов.
Разработка идет «от объекта» — представим, что необходимо спроектировать каталог продуктов. Сначала необходимо будет описать объекты каталога — продукты, их атрибуты (название, свойства и т.д.), а затем методы объектов — то, что можно делать с продуктами (создавать, изменять, каким-либо иным образом взаимодействовать с ними).
Понятия классов и методов (ООП)
Классы и методы являются основными элементами объектно-ориентированного программирования (ООП). Классы позволяют определять новые типы данных, объединяя данные и функции, работающие с ними, в единое целое. Методы — это функции, связанные с конкретными классами и работающими с их данными.
Определение объекта Объект — это сущность с конкретными характеристиками и функциями
Определение класса Класс — это шаблон для создания объектов. В Python класс определяется с помощью ключевого слова
class
.class Dog: pass
Здесь определен класс
Dog
, который пока ничего не делает. Ключевое словоpass
используется, чтобы указать, что тело класса пустое.Атрибуты класса Атрибуты класса — это переменные, которые принадлежат классу и доступны всем объектам этого класса. При создании объекта класса, они заполняются конкретными значениями
class Dog: species = "Canis familiaris" # атрибут класса
Теперь у класса
Dog
есть атрибутspecies
, который доступен всем объектам этого класса.Методы класса Методы — это функции, определенные внутри класса. Они работают с данными объекта или класса.
class Dog: def bark(self): # метод класса print("Гав!")
Метод
bark
— это функция, определенная внутри классаDog
. Обратите внимание на аргументself
, который ссылается на текущий объект.Конструктор Конструктор — специальный метод, вызываемый при создании объекта класса. В Python конструктор называется
__init__
.class Dog: def __init__(self, name, age): # конструктор self.name = name # атрибут объекта self.age = age # атрибут объекта
Теперь при создании объекта класса
Dog
необходимо передать ему имя и возраст.Создание объектов Чтобы использовать класс, нужно создать объект (или экземпляр) этого класса.
scooby = Dog("Скуби", 3) # создание объекта rex = Dog("Рекс", 5) # еще один объект
Оба объекта имеют свои собственные значения атрибутов
name
иage
.Доступ к атрибутам и методам Доступ к атрибутам и методам объекта осуществляется через точку.
print(scooby.name) # выведет "Скуби" rex.bark() # выведет "Гав!"
Наследование Наследование позволяет одному классу унаследовать свойства другого класса.
class Poodle(Dog): # Poodle наследует от Dog def speak_french(self): print("Oui Oui!")
Класс
Poodle
теперь обладает всеми методами и атрибутами классаDog
, а также своим собственным методомspeak_french
.Полиморфизм Полиморфизм позволяет объектам разных классов реагировать на одни и те же сообщения по-разному.
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-канале: Записки системного аналитика