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

Традиционные подходы, ориентированные на результат

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

  1. Неполная история. В конечном итоге мы расставляем приоритеты по результатам, не обращая внимания на то, как и почему они оказались такими, какими они были, что помогло бы нам понять всю историю.
  2. Тестирование программного обеспечения. В конечном итоге мы концентрируемся в первую очередь на общих результатах, упуская из виду важность тестирования деталей.
  3. Ложное ощущение безопасности. Если полагаться только на известные результаты, может создаться ложное ощущение безопасности, предполагая, что программное обеспечение безупречно и надежно. На самом деле результаты не гарантируют, что система работает должным образом.
  4. Неэффективная отладка. Когда возникают проблемы, отсутствие понимания мешает разработчикам определять основные причины проблем. Следовательно, драгоценное время тратится впустую на методы проб и ошибок вместо того, чтобы обратиться к действительному источнику сбоя.

Вот пример, который помогает продемонстрировать некоторые из этих проблем:

isEmployeeEligibleToSync(employee: Employee) {
  if (
    dayjs(employee.hireDate).isAfter(dayjs()) &&
    employee.email &&
    employee.workerType !== 'Contingent' &&
    employee.firstName &&
    employee.lastName
  ) {
    return true;
  }

  return false;
}

syncEmployee(employee: Employee) {
  const isEmployeeeEligibleToSync = isEmployeeEligibleToSync(employee);
  if (!isEmployeeeEligibleToSync) {
    this.logger.log(`Employee ${employee.id} is ineligible to sync.`);
    return;
  }
  // Employee is eligible to sync.
  // ... sync employee  
}

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

Вот условия приемлемости:

  1. hireDate сотрудника должно быть в прошлом (т. е. не в будущем).
  2. У сотрудника должен быть непустой email.
  3. workerType сотрудника не должно быть равно 'Contingent'.
  4. У сотрудника должны быть непустые firstName и lastName.

Если все эти условия соблюдены, функция возвращает true, указывая на то, что сотрудник имеет право на синхронизацию.

Проблема с этим подходом заключается в том, что он фокусируется исключительно на возврате результата (true или false)). Хотя определение результата имеет важное значение, код не учитывает причины, лежащие в основе результата. Сотрудник не смог встретиться.

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

  1. Отсутствие диагностической информации. Если функция возвращает false, разработчики остаются без контекста или диагностической информации о том, почему сотрудник не соответствует требованиям, что затрудняет отладку.
  2. Сниженная тестируемость. Двоичный возврат функции (true/false) затрудняет создание всесторонних тестов, которые независимо оценивают различные сценарии отказа. Это приводит к ложным срабатываниям при неправильных настройках теста, как показано в примере ниже:
describe('isEmployeeEligibleToSync', () => {
  it('should fail if the employee has no first name', () => {
    const employee = {
      id: 'employeeId',
      companyId: 'companyId',
      firstName: 'firstName',
      lastName: undefined,
      hireDate: new Date(),
      country: 'country1',
      email: 'email',
      workerType: 'workerType',
    };

    const isEmployeeEligibleToSync = isEmployeeEligibleToSync(employee);

    // This test will pass, even though the test setup is incorrect.
    expect(isEmployeeEligibleToSync).toBe(false);
  });
});

Расстановка приоритетов для обмена идеями

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

Давайте вернемся к приведенному выше примеру, расставив приоритеты для понимания:

getEmployeeIneligibilityReasons(employee: Employee) {
  const employeeIneligibilityReasons: Array<string> = [];if (dayjs(employee.hireDate).isAfter(dayjs())) {
    employeeIneligibilityReasons.push('Hire date is in the future.');
  }

  if (!employee.email) {
    employeeIneligibilityReasons.push('Employee has no work email.');
  }
  if (employee.workerType === 'Contingent') {
    employeeIneligibilityReasons.push('Employee is a contingent worker.');
  }
  if (!employee.firstName) {
    employeeIneligibilityReasons.push('Employee has no first name.');
  }
  if (!employee.lastName) {
    employeeIneligibilityReasons.push('Employee has no last name.');
  }
  return employeeIneligibilityReasons;
}

syncEmployee(employee: Employee) {
  const ineligibilityReasons = getEmployeeIneligibilityReasons(employee);
  if (ineligibilityReasons.length > 0) {
    this.logger.log(`Employee ${employee.id} is ineligible to sync because of the following reasons: ${ineligibilityReasons.join(', ')}`);
  }
  // Employee is eligible to sync.
  // ... sync employee
}

Предоставленный код, включающий функцию getEmployeeIneligibilityReasons и ее использование в функции syncEmployee, демонстрирует значительное улучшение по указанным ниже причинам:

  1. Улучшенная отладка: функция getEmployeeIneligibilityReasons явно фиксирует причины неприемлемости сотрудника, проверяя несколько условий. Возвращая конкретные причины сбоя, функция предоставляет ценную информацию о том, почему сотрудник не может быть синхронизирован.
  2. Ориентирован на пользователя: приносит пользу конечным пользователям, предоставляя им четкую и конкретную обратную связь, когда данные их сотрудников не соответствуют критериям приемлемости, улучшая взаимодействие с пользователем и уменьшая разочарование, помогая пользователям решить проблемы. и получить право на синхронизацию.
  3. Тестируемость. Функцию getEmployeeIneligibilityReasons легко тестировать благодаря четко определенной и целенаправленной цели. Разработчики могут создавать модульные тесты для проверки правильности каждой проверки приемлемости. Шансы на ложные срабатывания ограничены благодаря возможности тестировать каждый из сбоев по отдельности.
describe('getEmployeeIneligibilityReasons', () => {
  it('should fail if the employee has no first name', () => {
    const employee = {
      id: 'employeeId',
      companyId: 'companyId',
      firstName: 'firstName',
      lastName: undefined,
      hireDate: new Date(),
      country: 'country1',
      email: 'email',
      workerType: 'workerType',
    };

    const employeeIneligibilityReasons = getEmployeeIneligibilityReasons(employee);
    // This test will fail, since the test setup is incorrect.
    expect(employeeIneligibilityReasons).toEqual(['Employee has no first name.']);
  });
});

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

Уменьшение

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

getWallets(employee: Employee) {
  return this.companyWallets.filter((companyWallet) => {
    // Complex business logic to determine,
    // if employee is eligible for company wallet
  });
}

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

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

getWallets(employee: Employee) {
  return this.companyWallets.map((companyWallet) => {
    return {
      companyWallet,
      eligibility: {
        // Use the complex business logic to,
        // share details about an employee's eligibility for a given wallet
      },
    }
  });
}

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

Заключение

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

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

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