причины писать

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

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

проблема

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

import pino, {TransportTargetOptions} from "pino";
​
console.log("Hello world.");
​
function createLogger() {
    const targets: TransportTargetOptions[] = [
        {target: 'pino/file'} as TransportTargetOptions,
        {target: 'pino/file', options: {}, level: 'info'}
    ];t
    const levels = {
        apple: 80,
        banana: 75,
        cherry: 70,
        dates: 65,
        eggplant: 60,
    };
    return pino({
        transport: {targets: targets},
        customLevels: levels
    })
}
​
const logger = createLogger();
logger.info("echo");

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

Вы не сможете просмотреть журнал "echo", если не удалите {target: 'pino/file', options: {}, level: 'info'} или не измените его на {target: 'pino/file', options: {}, level: 'apple'}.

обходной путь

Да, это работает, пока мы заставляем пользователя использовать один из customLevels в TransportTargetOptions.level.

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

Или мы могли бы просто запретить поле уровня, попросить пользователей не указывать его в целях, так как это работает в {target: 'pino/file'} as TransportTargetOptions, вы примете «AnyScript» как двусмысленное от TransportTargetOptions до any или уточните его как type NoLevelTransportTargetOptions = Omit<TransportTargetOptions, "level">;?

Некоторые языки программирования, такие как golang, просто запрещают неявное приведение, что было бы полезно в нашем реальном случае, поскольку оно не позволяет {target: 'pino/file'} быть допустимым элементом TransportTargetOptions[] в коде javascript. Так же, как и методы перезаписи, те функции, которые обеспечивают скрытое поведение, упрощают использование, но затрудняют отладку.

отлаживать

что случилось

Несмотря на то, что он кажется заблокированным, но благодаря добавлению хинтера-оболочки на основе console.log,

мы могли бы узнать, что он не заблокирован, а замолчал.

установить леса

Создайте новый проект в своей среде IDE с помощью git clone https://github.com/pinojs/pino.git.

Вернемся к демонстрационному проекту и npm install ../pino, путь — это разветвленный пино, внутри которого можно делать трюки.

Теперь все, что вы добавили в разветвленном пино, может быть отражено в демонстрационном проекте.

Честно говоря, я добавил много console.log, чтобы проследить, что произошло в pino().

Почему я не могу разобраться быстрее?

Потому что есть свободные соединения, такие как target = bundlerOverrides['pino-worker'] || join(*__dirname*, 'worker.js'), которые используют файл исходного кода, а не символ для подключения, что де-факто отключает функцию IDE, которая может щелкнуть и перейти в код.

В golang, когда вы хотите включить свободное соединение, владельцы пакетов просто объявляют интерфейс и принимают его в конструкторах (функция NewXXX), чтобы сделать его открытым для расширения.

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

Используя типизированный язык программирования и избегая двусмысленной структуры (например, Map‹String, Map›, которая является объектом JSON) или большой структуры (в сочетании с десятками полей, а не несколькими слоями), мы могли бы упростить пользователю отслеживание контекста. при чтении кода.

первопричина

Как сказано в коде и журнале, это вызвано level = levels[dest.level], который пытался найти "info" в {"apple":80,"banana":75,"cherry":70,"dates":65,"eggplant":60}, undefined!

как это могло быть так

Это меня удивляет, так как он не объединяет customLevels с defaultLevels, что можно было бы сделать через function mappings in levels.js.

levels происходит от opts.levels в аргументах function multistream.

Уровни прозрачны от worker.js до mutlistream .

И с fullOptions.levels по options.levels в transport.

levels происходит от customLevels в результате слияния.

Но после печати opts.useOnlyCustomLevels не имеет значения, так как opts.levels не определено.

Проверьте журнал вокруг ключевого слова join.

И, в конце концов, мы отследили вход, который он использовал в function pino.

как бы мы исправили

Как подсказывает предыдущая трассировка, я попытался добавить levels: pino.levels.values в Options, и мы это сделали.

Но намекает ли на это официальный документ?

Да, но в другой теме. Это то, что мы сделали, но по-другому поэтому проигнорировали.

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

заключение

  1. Избегайте менее предсказуемых функций, таких как неявное приведение типов и перезапись методов.
  2. Используйте символ, а не файл исходного кода для подключения, используйте типизированные и неплоские (многоуровневые, если они огромные) параметры, чтобы было легче запоминать контекст и отслеживать процедуру программы.
  3. Поощряйте выполнение одного действия только одним способом, чтобы в руководстве можно было охватить больше случаев.
  4. Как и в golang, нулевое значение как пустое значение, без null/undefined, лучше паниковать, если параметры неожиданны.