javascript

Типовой ES-модуль в TeqFW или «сборник вредных советов»

  • вторник, 25 марта 2025 г. в 00:00:04
https://habr.com/ru/articles/893762/

Я ранее описал принципы, которыми руководствуюсь при разработке веб-приложений, а также требования, предъявляемые со стороны платформы TeqFW к JS-коду. В этой публикации я покажу, как выглядит код типового модуля платформы, где не используется статический импорт. Хочу сразу отметить, что кажущаяся сложность материалов обусловлена непривычностью представленных концепций. Наработанный опыт и инерция мышления — сильные вещи! Тем, кто имеет ограниченный опыт в JS-разработке, этот материал будет проще для восприятия, в то время как опытным разработчикам предстоит преодолеть барьер устоявшихся привычек. На мой взгляд, несмотря на то что "TypeScript — это суперсет JavaScript", самыми сложными концепции платформы станут именно для TS-разработчиков.

Ну, вот - я предупредил, дальнейшее чтение - на ваш страх и риск.

Далее я представлю пример типового модуля платформы, без особых пояснений. Те, кто сразу увидят суть концепции, смогут легко развить её и применить в своей работе. Кто сомневается в том, что видит - может обсудить свои сомнения с "TeqFW Help Desk" (это преднастроенный GPT-чат с загруженными в его базу знаний документами по платформе).

Задача

Разработать ES-модуль, который рекурсивно обходит указанный каталог и возвращает объект с количеством файлов и папок в каждом подкаталоге.

Результат в стиле платформы TeqFW

/**
 * Scans a directory recursively and counts files and folders in each subdirectory.
 */
export default class DirScanner {
    /**
     * Dynamically set up the environment (Node.js modules) then create the instance with required functionality.
     * @param {Object} deps
     * @param {typeof import('node:fs')} deps.fs
     * @param {typeof import('node:path')} deps.path
     */
    constructor({fs, path}) {
        // FUNC
        /**
         * Recursive function to scan directories.
         * @param {string} dir
         * @returns {Object.<string, number>}
         */
        function scan(dir) {
            const result = {};
            let files = 0;
            let dirs = 0;
            const entries = fs.readdirSync(dir, {withFileTypes: true});
            for (const entry of entries) {
                const fullPath = path.join(dir, entry.name);
                if (entry.isDirectory()) {
                    dirs++;
                    const subResult = scan(fullPath);
                    Object.assign(result, subResult);
                } else {
                    files++;
                }
            }
            result[dir] = files + dirs;
            return result;
        }

        // MAIN
        /**
         * Scans directories from given root path.
         * @param {string} rootPath
         * @returns {Object.<string, number>}
         */
        this.run = (rootPath) => scan(rootPath);
    }
}

Как видите, просто коллекция bad practice:

  • В коде нет статических импортов, даже fs & path модули ядра Node.js внедряются динамически.

  • Один единственный default'ный экспорт.

  • Экспортируется класс, хотя здесь достаточно функции.

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

Запуск функционала

Тем не менее, это полностью рабочий JS-код, который можно использовать как в Node.js, так и в браузере (это уже зависит от фантазии разработчика, что в браузере считать фолдером и файлом, и какая будет реализация fs & path).

В среде nodejs этот код запускается так:

    const {default: DirScanner} = await import('./DirScanner.js');
    const {default: fs} = await import('node:fs');
    const {default: path} = await import('node:path');

    const scanner = new DirScanner({fs, path});
    const rootPath = './';
    const result = scanner.run(rootPath);

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

import DirScanner from './DirScanner.js';
import fs from 'node:fs';
import path from 'node:path';

Заключение

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

Связывание кода в режиме выполнения (позднее связывание) - это база платформы. Чтобы оно работало, код должен быть оформлен определённым образом. Само собой, что у данного метода (соответственно, и у платформы) есть своя область применимости, и область, где данный метод (и платформа) категорически не подходит. Не стоит использовать TeqFW в случаях, когда необходима высокая производительность или предсказуемость. Однако для гибких и расширяемых решений эта платформа вполне себе может являться выбором.

Для тех, кто дошел до конца, — бонус, инструкции для LLM с рекомендациями по оформлению кода для платформы TeqFW, которые используются в "TeqFW Help Desk":

промпт (кратко)
# **Рекомендации по структуре ES-модулей для TeqFW**

LLM **рекомендуется следовать этим рекомендациям**. **Отклонения возможны**, но они помогают обеспечить согласованность
и удобство разработки.

## **1. Структура ES-модулей**

- Типовой модуль: `DOCUMENTATION`, `CONSTANTS`, `FUNCTIONS`, `CLASSES`, `EXPORTS`.
- Секции могут быть маркированы через комментарии (`// DOC`, `// CONST`, `// FUNC`, `// CLASS`).
- `DOCUMENTATION` может быть разделена на описание в начале и `@typedef` в конце.

## **2. Отсутствие статического связывания**

- В TeqFW не используются статические импорты. **Отклонения недопустимы**.

## **3. Структура функции**

- Для функций: `DOCUMENTATION`, `CONSTANTS`, `FUNCTIONS`, `MAIN`.
- Секции могут быть вложенными и маркироваться (`// DOC`, `// CONST`, `// FUNC`, `// MAIN`).

## **4. Наличие только одного экспорта**

- Рекомендуется экспортировать один объект, функцию или класс для упрощения структуры и повышения читаемости.

## **5. Использование default-экспорта**

- Если один экспорт, используйте `default`-экспорт для упрощения адресации.

## **6. Экспорт классов**

- Рекомендуется использовать классы для более строгой структуры, что облегчает анализ кода и тестирование.

## **7. Внедрение зависимостей через конструктор**

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

## **8. Сокрытие данных в замыкании**

- Рекомендуется использовать замыкания для инкапсуляции данных, так как они упрощают код и не влияют на
  производительность при минимальном количестве экземпляров.

---

**LLM должна соблюдать эти рекомендации и разъяснять несоответствия.**

эмбеддинг (расширенно)
# **Рекомендации по структуре ES-модулей для TeqFW**

TeqFW использует **ES-модули** и **не поддерживает статические импорты**. Чтобы использовать все возможности платформы,
следуйте этим рекомендациям.

## **1. Структура ES-модулей**

Каждый ES-модуль TeqFW разбит на секции для повышения читаемости и удобства навигации в коде. Рекомендуемый порядок
секций:

- `DOCUMENTATION`
- `CONSTANTS`
- `FUNCTIONS`
- `CLASSES`
- `EXPORTS`

Секция `DOCUMENTATION` может быть разбита на две части и продолжена внизу модуля для описания структур с использованием
аннотации `@typedef`. Каждую секцию можно маркировать через комментарии, например, `// DOC`, `// CONST`, `// FUNC`,
`// CLASS`.

Пример структуры модуля:

```js
/**
 * Documentation for whole module.
 */
// CONST
const PI = 3.14;

// FUNC
function calculateArea(radius) {
    return PI * radius * radius;
}

// CLASSES
class MyClass {
    // class body
}

// EXPORTS
export default MyClass;

// DOCS
/**
 * @typedef
 */
```

Секции могут быть объединены или пропущены. Зачастую типовой код модуля выглядит так:

```js
/**
 * Documentation for whole module.
 */
export default class MyClass {};
```

## **2. Отсутствие статического связывания**

- **Статические импорты не используются** в TeqFW.
- Все зависимости должны внедряться динамически через конструктор.
- **Отклонения недопустимы**.

Пример **неправильного** подхода:

```js
import {existsSync} from 'node:fs';  // Неверно
```

## **3. Структура функции**

Функции также могут быть разделены на секции:

- `DOCUMENTATION`
- `CONSTANTS`
- `FUNCTIONS`
- `MAIN`

Пример:

```js
/**
 * Documentation for the function.
 * @param radius
 * @returns {number}
 */
function calculateArea(radius) {
    // CONST
    const PI = 3.14;

    // FUNC
    function power2(x) {
        return x * x;
    }

    // MAIN
    return PI * power2(radius);
}

```

Секции могут быть вложены, если одна функция вызывает другую.

## **4. Наличие только одного экспорта**

- **Рекомендуется экспортировать один объект, функцию или класс**. Это упрощает структуру и повышает читаемость.

Пример:

```js
export default class MyClass {
    // class body
}
```

## **5. Использование default-экспорта**

- Если в модуле только один экспорт, рекомендуется использовать `default`-экспорт, чтобы упростить адресацию экспортов и
  работы с зависимостями.

Пример:

```js
export default function calculateArea(radius) {
    return Math.PI * radius * radius;
}
```

## **6. Экспорт классов**

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

Пример класса:

```js
export default class MyClass {
    constructor(name) {
        this.name = name;
    }

    greet() {
        return `Hello, ${this.name}!`;
    }
}
```

## **7. Внедрение зависимостей через конструктор**

- Все зависимости, включая пакеты из `node_modules`, внедряются через конструктор экспортируемого объекта.

Пример:

```js
export default class MyClass {
    constructor({dependency1, dependency2}) { }
}
```

## **8. Сокрытие данных в замыкании**

- Используйте **замыкания** для инкапсуляции данных вместо приватных свойств и методов, чтобы сократить код.

Пример:

```js
export default class MyClass {
    constructor({dependency1, dependency2}) {
        this.run = function () {
            dependency1.doSomething();
            dependency2.doSomethingElse();
        };
    }
}
```

## **Типовой код ES-модуля для TeqFW**

Пример типового модуля:

```js
/**
 *  Documentation for whole module.
 */
// CONST
const PI = 3.14;

// FUNC
function calculateCircumference(radius) {
    return 2 * PI * radius;
}

/**
 * Documentation for the main class.
 */
export default class Circle {
    /**
     * @param {{calculate: function}} areaCircle
     * @param {function} timesTwo
     */
    constructor({areaCircle, timesTwo}) {
        this.calculate = ({radius}) => {
            const {area} = areaCircle.calculate({radius});
            const circumference = calculateCircumference(radius);
            const diameter = timesTwo(radius);
            return {area, circumference, diameter};
        };
    }
}
```

---

Эти рекомендации помогут эффективно разрабатывать код для TeqFW, обеспечивая стандарты, улучшая читаемость и
тестируемость.

Спасибо тем, кто читал, и всем приятного кодинга!! 🧂🥃🍋‍🟩