Что я понял за полтора года преподавания

Полтора года назад я провёл своё первое занятие в Нетологии. С тех пор я читал лекции, проверял домашние задания, менторил дипломные проекты и даже сам был студентом на одном из курсов. За это время я смог осознать для себя некоторые штуки о процессе обучения, которыми решил поделиться.

Будьте аккуратнее с новыми терминами

Если вы объясняете новичкам незнакомые термины, то это стоит делать понятиями, которые им уже известны. Следовать этому правилу труднее, чем кажется.

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

Но как только вы начнёте объяснять это другим людям, у них вполне оправданно появится куча вопросов. Что за маржины, что значит «схлопываются», как сбрасывать флоты, зачем они нужны, откуда отступы, зачем они там?

Чтобы объяснить эти тонкости, надо использовать уже знакомые понятия. Потому что иначе вы не только не объясните, что хотели, но и отобьёте интерес к теме. Никому не хочется разбираться в непонятной фигне, по крайней мере — в чересчур непонятной.

Поэтому объяснение нового термина должно:

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

Избегайте путаницы

Если у термина есть синонимы (флот ⇔ плавающий элемент), надо это пояснить на старте.

Можно также объяснить, почему эти термины взаимозаменяемы. (Флот переводится, как плавающий, а используется такой элемент, чтобы его «обтекал» текст. Поэтому он как бы плавающий, это такой грубый перевод.)

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

Автоматизируйте всё, что можно

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

Часть ошибок — заблуждения, которые возникают при погружении в тему. На такие ошибки стоит заранее приготовить развёрнутое объяснение, почему заблуждение — это именно заблуждение, и как решить задачу было бы правильнее.

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

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

Объясняйте на примерах

Объяснение с примерами понятнее. Я пробовал объяснять и абстрактно, и на примерах — на примерах лучше.

Абстракция — ресурсозатратная штука. Даже чтобы представить что‑то уже знакомое, требуется внимание и сосредоточенность. Чтобы представить какое‑то новое понятие, ресурсов потребуется больше.

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

Делайте перерывы во время занятия

Во время объяснений стоит делать небольшие перерывы.

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

Ученикам перерыв даёт время передохнуть и восстановить внимание. Вам — перевести дыхание.

Стимулируйте обратную связь

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

Если вопрос задан расплывчато, и вам непонятно, как ответить, просто уточните, что имелось в виду. И, если в чём‑то сомневаетесь, не стесняйтесь гуглить прямо во время занятия. Я так и говорю: «не уверен, давайте прямо сейчас и загуглим».

Пейте воду во время занятия

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

Пить воду ещё можно, если чувствуете, что плывёте или сильно волнуетесь. Это работает как микротаймаут: пока пьёте воду, можно перевести дух, сформулировать мысль и продолжить.

Дозируйте время на ревью

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

Дело в монотонности, от неё надо избавляться. Чередовать темы, тасовать задачи, менять вид деятельности. Короче, делать всё, чтобы ревью не приедалось. Иначе отвечать свежо не получится и просто пропустить вопрос, на который стоит дать более развёрнутый ответ, или ошибку в решении.

Получайте пользу

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

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

Куршская коса, июнь 2018

Куршская коса — это вытянутый полуостров на севере Калининградской области, отделяющий Балтийское море от Куршского залива. На Российской части косы три посёлка (Лесной, Рыбачий, Морское) и национальный парк.

Въезд в парк платный, по 150₽ с человека, плюс экологический сбор, если ехать на машине. Мы поехали накануне праздников и на въезде попали в пробку :–)

Коса состоит по большей части из песка, и пляжи на ней с мелким песком.

Пляж на косе

В Рыбачьем действует орнитологическая станция Фрингилла, на которой кольцуют птиц. Там стоят ловушки, которые работают как примитивный лабиринт. «Вход» в ловушку широкий, но по мере углубления внутренние створки сужаются. Получается горлышко, через которое птицы обратно вылететь не могут.

«Вход» в птичью ловушку
Ловушка изнутри

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

«Выход» из ловушки

Чуть дальше по маршруту — Танцующий лес. Стволы деревьев в этом лесу растут не прямо, а извиваются и скручиваются. По одной из версий причина искривлений — гусеницы, которые повреждают побеги. Было бы круто там оказаться осенью.

Деревья в танцующем лесу — 1
Деревья в танцующем лесу — 2
Деревья в танцующем лесу — 3
Деревья в танцующем лесу — 4
Деревья в танцующем лесу — 5

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

Дерево за ограждением

На косе много песчаных дюн. Песок сдувается ветром, дюны «ловят» песчаными ловушками.

Ловушки для песка
Ловушки для песка

Автором закрепления дюн считается Франц Эфа, в честь которого назвали высоту на самой высокой дюне.

Вид с высоты Эфа — 1
Вид с высоты Эфа — 2

Посёлок Морское несколько раз «переезжал» из‑за подвижных песков. Был недалеко отсюда:

Бывшее место, где стоял посёлок

Теперь там:

Нынешнее место, где стоял посёлок

Е2Е тестирование Койна

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

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

Инструменты

End‑to‑end (Е2Е) тесты — это интеграционные тесты, которые взаимодействуют с интерфейсом так, как это делал бы пользователь. Для них я попробовал несколько инструментов, но больше всего мне понравился Сайпрес.

После его установки и запуска в корне проекта появляется папка cypress/. Внутри неё: integration/ — там находятся сами тесты, и support/ — там вспомогательные функции (об этом подробнее дальше).

Вход в приложение

Вход в сам Койн пока что по приглашениям, и для входа нужно ввести правильный код. Поэтому первый сценарий, с которым сталкивается пользователь — ввод кода для входа.

Исхода у сценария два: успешный и неуспешный вход. Пишем тест на первый случай.

describe('Login window', () => {
  it('Valid code passes login', () => {
    // здесь будет логика теста
  })
})

Нам надо зайти в приложение и попасть на страницу логина. На страницу мы зайдём с помощью команды visit, передав аргументом адрес:

// к примеру проверяем приложение локально
cy.visit('localhost:8081')

Проверим, существует ли форма логина, и пустое ли поле ввода. Проверяем наличие нужных блоков и то, что поле пустое:

cy.get('.login').should('have.length', 1)
cy.get('.login-code').should('be.empty')

Выборка элементов в Сайпресе работает похоже на Джейквери. Например, здесь мы выбираем элементы по классам. Метод should проверит, что на странице только 1 элемент с классом login, а элемент с классом login‑code пустой.

Набор текста в настоящих полях ввода в Сайпресе делается через метод type. Но в Койне клавиатура ненативная и настоящих полей ввода там тоже нет. Вместо них — блоки, в которых отображается «набранная» последовательность. Чтобы набрать какой‑то код на нашей клавиатуре, надо «нажать» клавишу с нужной цифрой. Мы будем разбивать код на символы и нажимать на клавиши с указанными символами.

Метод contains ищет элемент, который содержит переданный в аргументе текст, в нашем случае — символ. Метод closest находит ближайшего родителя с указанным селектором, в нашем случае — классом button.

const chars = code.toString().split('')

chars.forEach(char => {
  cy.get('.keyboard')
    .contains(char)
    .closest('.button')
    .click()
})

Когда код набран, можно нажать на красную кнопку, чтобы «отправить» код.

cy.get('.button.is-enter').click()

Код теста целиком будет выглядеть так:

describe('Login window', () => {
  it('Valid code passes login', () => {
    // зайти в приложение
    cy.visit('localhost:8081')

    // проверить форму
    cy.get('.login').should('have.length', 1)
    cy.get('.login-code').should('be.empty')

    // ввести код
    const chars = validCode.toString().split('')

    chars.forEach(char => {
      cy.get('.keyboard')
        .contains(char)
        .closest('.button')
        .click()
    })

    // нажать энтер
    cy.get('.button.is-enter').click()
  })
})

После запуска Сайпрес запустит браузер, прогонит сценарий и покажет, прошёл тест или нет. Выглядит это так: Результат выполнения теста на вход в приложение

Рефакторинг и второй сценарий

Теперь перейдём к тесту с неправильным кодом. Он будет таким же, только код будет другой, и результат будет отличаться. Чтобы не дублировать код, мы можем вынести повторяющиеся действия в функции. Но у Сайпреса есть более изящное решение — команды.

Команды похожи на плагины. Вы описываете функцию‑команду, и она становится доступной глобально через cy. Команды хранятся в папке support/, их можно как угодно разделять по файлам. Главное — импортировать их в support/index.js, чтобы Сайпрес их увидел.

Адрес страницы‑приложения меняться не будет, поэтому вход в приложение вынесем в команду enterApp, а сам адрес запишем в fixtures/common.json:

import {baseUrl} from '../fixtures/common.json'

Cypress.Commands.add('enterApp', () =>
  cy.visit(baseUrl))

Проверка формы тоже будет повторяться, поэтому вынесем её в команду appContainsEmptyLoginForm.

Cypress.Commands.add('appContainsEmptyLoginForm', () => {
  cy.get('.login').should('have.length', 1)
  cy.get('.login-code').should('be.empty')
})

Я предпочитаю называть команды либо:

  • глаголом с действием, которое надо выполнить: enterApp;
  • предикатом для проверок: appContainsEmptyLoginForm.

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

Ввод чисел на клавиатуре нам тоже понадобится в других тестах приложения. Поэтому его мы превратим в команду keyboardType.

Cypress.Commands.add('keyboardType', (str) => {
  const chars = str.toString().split('')

  chars.forEach(char => {
    cy.get('.keyboard')
      .contains(char)
      .closest('.button')
      .click()
  })
})

Нажатие на «энтер» нам тоже пригодится в других местах:

Cypress.Commands.add('pressEnter', () => {
  cy.get('.button.is-enter').click()
})

В итоге код теста станет таким:

describe('Login', () => {
  it('Valid code passes login', () => {
    cy.enterApp()
    cy.appContainsEmptyLoginForm()
    cy.enterLoginCode(validCode)
    cy.get('.login').should('have.length', 0)
  })
})

Теперь напишем тест на неправильный код:

it('Invalid codes dont pass login', () => {
  cy.enterApp()
  cy.appContainsEmptyLoginForm()
  cy.enterLoginCode(invalidCode)
  cy.get('.login').should('have.length', 1)
})

Как мы видим, первые две строки повторяются, поэтому их можно вынести к сетап теста:

describe('Login', () => {
  beforeEach(() => {
    cy.enterApp()
    cy.appContainsEmptyLoginForm()
  })

  it('Valid code passes login', () => {
    cy.enterLoginCode(validCode)
    cy.get('.login').should('have.length', 0)
  })

  it('Invalid codes dont pass login', () => {
    cy.enterLoginCode(invalidCode)
    cy.get('.login').should('have.length', 1)
  })
})

После запуска увидим такую картину: Результат выполнения отказного теста на вход в приложение

Если какой‑то тест свалится, то в логах будет описано, что не совпало. Сайпрес офигительно описывает ошибку: показывает, какой узел её вызвал, что не совпало, и сохраняет снапшот состояния. То есть можно ткнуть мышью в этот баг, и справа отобразится состояние страницы в это время! Описание ошибки в тестировании

Сценарий создания бюджета

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

Заведём бюджет из 10000 попугаев на 10 дней. Приложение запишет в бюджет только 95% от той суммы, которую вводим, чтобы план не оказался впритык. Значит, после сохранения бюджет будет содержать 9500 попугаев.

describe('Budget creation', () => {
  before(() => {
    // команда для быстрого логина в приложение, минуя форму
    cy.login()
    cy.enterApp()    
    // открывает настройки бюджета
    cy.openBudgetSettings()
  })

  it('Inputs the budget sum and saves it', () => {
    cy.keyboardType('10000')
    cy.pressEnter()

    cy.get('.budget')
      .contains('9500')
  })
})

Дальше выбираем срок. Мы выберем 10 дней, поэтому нам надо выделить 10‑й пункт в крутилке с датами. Проверяем, что даты до выбранной включительно стали красными и что в бюджете появилась строка с суммой на день.

it('Inputs the budget time and saves it', () => {
  cy.get('.datepicker-item')
    // индексы начинаются с нуля, 10-й элемент — eq(9)
    .eq(9)
    .click()

  cy.get('.datepicker-item.has-red-color')
    .should('have.length', 10)

  cy.get('.dialogue-secondary')
    .contains('на 10 дней. 950 в день')

  cy.get('.button.is-fixed-rb')
    .click()
})

После сохранения проверяем, правильно ли посчиталась сумма на день. Проверять содержимое счётчиков нам понадобится и в других тестах, поэтому создадим команду:

Cypress.Commands.add('counterContains', (content) => {
  cy.get('.mainContent .dialogue .counter')
    .contains(content)
})

it('Tests todays limit', () => {
  cy.counterContains(950)
})

И что сохранилась запись в истории о создании бюджета:

Cypress.Commands.add('budgetRecordContains', (sum, days) => {
  const $lastRecord = (selector) =>
    cy.get('.timeline')
      .find(selector)
      .last()

  $lastRecord('.record--budget').contains(sum)
  $lastRecord('.record--budget').contains(days)
})

it('Tests history record', () => {
  cy.budgetRecordContains(9500, 10)
})

После запуска увидим такую картину: Результат выполнения теста на создание бюджета

Основной сценарий трат

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

describe('Tests spendings', () => {
  context('When budget is not set', () => {
    beforeEach(() => {
      cy.login()
      cy.enterApp()
    })

    it('Spends 400 parrots for helpful stuff', () => {
      // ...
    })
  })
})

Каждую трату мы можем сделать полезной или вредной. Чтобы проверить все случаи и не повторять код, напишем функцию spendMoneyOnce, которая будет заниматься тратами.

// команда для включения и выключения категорий
Cypress.Commands.add('toggleCategory', (type='helpful') => {
  cy.get(`.category.is-${type}`).click()
})

const spendMoneyOnce = (amount, category='unknown') => {
  amount = `${amount}`
  cy.keyboardType(amount)
  cy.get('.numberDisplay-value')
      .contains(amount)

  if (category !== 'unknown') {
    cy.toggleCategory(category)
  }

  cy.pressEnter()
}

И функцию, которая будет проверять, сохранилась ли трата:

const spendSaved = (amount, category) => {
  // команда для проверки последней записи в истории
  cy.lastRecordContains(amount, category)
}

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

it('Spends 400 parrots for helpful stuff', () => {
  const [amount, category] = [400, 'helpful']
  spendMoneyOnce(amount, category)
  spendSaved(amount, category)
})

it('Spends 400 parrots for harmful stuff', () => {
  const [amount, category] = [400, 'harmful']
  spendMoneyOnce(amount, category)
  spendSaved(amount, category)
})

Траты из заполненного бюджета

Теперь протестируем трату, когда бюджет задан. У меня описано много сценариев, но здесь я покажу два. В первом трата меньше дневного лимита, и сумма на день остаётся такой же, во втором — трата больше, и сумма на день уменьшается.

context('When budget is set, 950 for today', () => {
  beforeEach(() => {
    cy.login()
    cy.enterApp()
    // команда для быстрого создания бюджета с указанными параметрами
    cy.createBudgetWith(10000, 10)
  })

  it('Spends amount smaller than the limit for today', () => {
    testSpendWithActiveBudget({
      amount: 100,
      forToday: 850
    })
  })

  it('Spends amount bigger than the limit for today', () => {
    testSpendWithActiveBudget({
      amount: 1000,
      forToday: -50,
      newDayLimit: '944,44'
    })
  })
})

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

const testSpendWithActiveBudget = ({
  amount,      // число
  forToday,    // число
  newDayLimit, // форматированная строка
}) => {
  spendMoneyOnce(amount, 'unknown')
  cy.lastRecordContains(amount, 'unknown')

  // трата меньше, чем лимит на сегодня
  if (forToday > 0) {
    cy.counterContains(forToday)
  }
  // трата больше, приложение пересчитает сумму на день
  else {
    cy.counterRowContains('Новая сумма на день', 0)
    cy.counterRowContains(newDayLimit, 0)

    cy.counterRowContains('На сегодня дно пробито', 1)
    cy.counterRowContains(forToday, 1)
  }
}

Меняем даты в браузере

Осталось протестировать, как себя ведёт бюджет и история, если приложение запускают через день или несколько дней.

context('Tests next day settings', () => {
  beforeEach(() => {
    cy.login()
    cy.enterApp()
    cy.createBudgetWith(10000, 10)
  })
})

Сперва проверим, что непотраченные деньги попадают в копилку:

it('Tests next day safe record', () => {
  spendMoneyOnce(400)
  cy.skipDay()
  cy.safeRecordContains(550)
})

Затем, что сумма на день осталась той же, если пользователь не вышел за вчерашний лимит

it('Tests next day limit after spend less than prev limit', () => {
  spendMoneyOnce(400)
  cy.skipDay()
  cy.counterContains(950)
})

И что сумма уменьшится, если пользователь вышел за лимит:

it('Tests next day limit after spending more than prev limit', () => {
  spendMoneyOnce(1000)
  cy.skipDay()
  cy.counterContains('944,44')
})

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

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

Первым агументом ему передаём таймштамп момента, в который надо перевести часы. Вторым — функции и объекты, которые будут изменены во время выполнения. Нам достаточно подменить только объект Date.

Между тестами сбрасывать настройки времени через restore необязательно, потому что Сайпрес делает это сам. Но если мы запускаем skipDays внутри одного теста несколько раз, то чтобы перетереть предыдущие настройки, надо вызвать restore.

Метод reload перезагружает страницу — будто пользователь заходит в приложение спустя указанное время.

Cypress.Commands.add('skipDays', (count=1, from=Date.now()) => {
  cy.clock().then(clock => clock.restore())
  cy.clock(from + (count * MSECONDS_IN_DAY), ['Date'])
  cy.reload()
})

// синоним для cy.skipDays(1)
Cypress.Commands.add('skipDay', () => {
  cy.skipDays(1)
})

Результат

Самое кайфовое в таких тестах — смотреть, как они работают. Вот ускоренный видосик со всеми тестами, которые есть в проекте. Самые глазастые смогут там найти настоящий рабочий пароль для входа в приложение :–)

Видео с работой всех тестов в проекте

Ссылки по теме

Как я избавился от иррационального страха потерять работу

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

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

«Тоже мне проблема!»

Ну да, кажется надуманной фигнёй. Страх на то и иррациональный, что причин вроде бы нет, а страх есть.

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

Эта же проблема мешала работать. Из‑за страха я мог реагировать на критику реактивно, быстрее уставал и не мог нормально отдохнуть. Было ощущение, что мозг пытается майнить бетховенов на фоне, пока я что‑то делаю. Короче, меня это достало и я решил пойти к психологу.

Первый сеанс — всегда знакомство

Не скажу, что с первого раза всё стало понятно, и проблема исчезла. Нет, конечно. Первый сеанс — это знакомство: мало кто просто так откроется незнакомому человеку.

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

Придётся делать «домашку»

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

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

Первый результат — не финальный

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

Это не значит, что копание не закончится и нельзя радоваться прогрессу. Наоборот — радоваться надо, не надо думать, что всё будет быстро и просто.

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

Сейчас рабочая версия в том, что в детстве я «примерил» на себя роль взрослого с кучей ответственностей. Но тогдашний страх был оправдан: ни квалификации, ни опыта. Из‑за их отсутствия появился страх не оправдать ожиданий, потерять работу, и как следствие — желание сделать всё идеально с первого раза.

Решение

Возможное решение проблемы состоит из 4 частей.

  • Осознать, что ответственности меньше, чем кажется. Не всё на свете зависит от моих действий, есть куча внешних факторов, которые я не могу контролировать.
  • Собрать обратную связь от людей, мнение которых я ценю. По этой обратной связи определить действительный уровень квалификации и работать с этим: поднимать уровень до приемлемого, если он таким не кажется.
  • Работать с ожиданиями других людей, чтобы не беспокоиться, что меня неправильно поняли.
  • Больше ценить себя. Не загонять себя в рамки «должен», «обязан», «надо». Уделять больше внимания таким штукам, как «хочу» и «неплохо бы».

Первый пункт не значит, что надо наплевательски относиться к работе. Это бы противоречило пункту 2 — ведь своё мнение как специалиста о себе мне тоже ценно. Последний пункт меньше относится к работе и больше к образу жизни в целом.

«И чо, работает?»

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

«А нафиг ты всё это рассказываешь?»

Я тоже вначале думал, кому какое дело. Но пришёл один парень в телеграм и сказал правильную мысль:

Диалог из телеграма

Вот я и написал.

Документация

Польза документации по проекту не в только том, чтобы она описывала «что это за хрень», а ещё и «почему эта хрень сделана вот так».

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

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

Раньше ↓