Жизнь жуков

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

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

// NumberOfGuestsSelectBoxWrapper-tests.jsx
decribe('NumberOfGuestsSelectBoxWrapper', () => {
  it('should use value provided by flux store', => {
    const { getPropsFromFluxState } = require('../NumberOfGuestsSelectBox'); 
    const mockFluxState = {
      enquiryForm: {
        numberOfGuests: 5
      }
    };
    assert.equal(getPropsFromFluxState(mockFluxState).value, 5);
  });

  it('should handle falsey', => {
    const { getPropsFromFluxState } = require('../NumberOfGuestsSelectBox'); 
    const mockFluxState = {
      enquiryForm: {
        numberOfGuests: 0
      }
    };
    assert.equal(getPropsFromFluxState(mockFluxState).value, 1);
  });

  it('should handle falsey', => {
    const { getPropsFromFluxState } = require('../NumberOfGuestsSelectBox'); 
    const mockFluxState = {
      enquiryForm: {}
    };
    assert.equal(getPropsFromFluxState(mockFluxState).value, 1);
  });
});

// NumberOfGuestsSelectBoxWrapper.jsx
import React from 'react';

import NumberOfGuestsSelectBox from './NumberOfGuestsSelectBox';
import createContainer from '../../flux/utils';


class GuestsSelectorWrapper extends React.Component {
  render() {
    const { value } = this.props;
    return (
      <NumberOfGuestsSelectBox
        value={value}
        name='number_of_guests'
        className='ofs-enquiry-form-field'
      />
    );
  }
}

GuestsSelectorWrapper.propTypes = {
  value: React.PropTypes.string.isRequired,
};


getPropsFromFluxState = state =>  ({
  value: state.enquiryForm.numberOfGuests | 1,
});


export default createContainer(GuestsSelectorWrapper, getPropsFromFluxState);


// utils.jsx

export createContainer (TargetComponent, getPropsFromFluxState) => {
  const props = getPropsFromFluxState(fluxState); // fluxState is provided by flux framework
  return (<TargetComponent ...props >);

}

Вы уже заметили ошибку? Да? Нет? В любом случае может потребоваться некоторое объяснение:

1) Компоненты делятся на умные и тупые. вот почему.

2) createContainer возвращает экземпляр компонента, которому в качестве реквизита было передано состояние из хранилища потоков.

3) Если state.enquiryForm.numberOfGuests имеет значение false, по умолчанию используется значение 1.

4) У нас есть несколько тестов, которые подтверждают, что логика соответствует ожиданиям (1 для ложных, 5 для иных).

Модульные тесты прошли, и мы были счастливы, когда вручную протестировали компонент в браузере — поле выбора «запомнило» значение. Здорово. Развертывать!

Так почему же был баг? В отчете было: «количество гостей в поле выбора добавляет 1!»

После проверки компонента и кода мы пришли к выводу «нет» — компонент не добавил единицу к значению — когда мы выбрали его в форме поиска, мы получили его в форме запроса. Когда мы выбрали пять на странице поиска, мы получили пять на странице поиска. когда мы выбираем четыре на странице поиска, мы получаем пять на странице запроса. Чего ждать?

Исследование ошибки

Да, мы смогли воспроизвести ошибку. Здорово. Мы составили таблицу «значение, которое мы выбрали на странице поиска» против «значения, предварительно заполненного в форме запроса»:

Каждое четное число добавляет к себе единицу!? Хорошо…

Вернитесь к коду и посмотрите, видите ли вы ошибку. Нет?

Побитовое ИЛИ, логическое ИЛИ

Есть большая разница между | и ||. Из-за простой опечатки вместо логического ИЛИ использовалось побитовое ИЛИ. Результатом отсутствия трубы было такое неожиданное поведение.

Побитовое ИЛИ выполняет логическое ИЛИ для каждого бита в числовом значении — таким образом, пять будут преобразованы в 0101, а один будет преобразован в 0001, и каждый бит будет ИЛИ — если любой из битов равен 1, используйте 1 в позиции возвращаемого значения:

Уроки выучены.

«Напишите больше модульных тестов» — это не ответ на эту проблему. В конце концов, юнит-тесты были написаны, а баг всё равно пролез. Мы могли бы написать тесты, специально нацеленные на ошибки «побитового ИЛИ», но это устанавливает приоритет над всеми подобными сценариями. Это заняло бы непомерно много времени. Более того, модульные тесты предназначены для подтверждения логики, а не для обнаружения ошибок. Этот тип ошибки может быть более эффективно обнаружен с помощью других средств.

Эта ошибка была упущена тремя разработчиками во время проверки кода. Указывает ли это на человеческую проблему с процессом проверки? Думаю, нет. Иногда люди видят то, что ожидают увидеть. Имея это в виду, как сказал У. Эдвардс Деминг: 100-процентная проверка на 100% эффективна только в том случае, если инспектор обнаруживает 100% дефектов в 100% случаев… что невозможно из-за человеческой природы. В конце концов, если бы люди были такими точными, не было бы никаких дефектов.

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