Pull to refresh

Comments 29

Как всегда, притянули за уши.


Проблема заключается в самом ключевом слове const. То, как ведут себя константы, объявленные с его помощью, не соответствует тому, что большинство разработчиков ассоциируют с понятием «константа».

"Большинство разработчиков". Покажите мне этих разработчиков. Если человек использует const и до сих пор не понял, что это значит — либо разработчик никакой (передача по ссылке? это что такое?), либо никогда и не пробовал понять. Это проблема "большинства разработчиков", но никак не языка.


Однако код, написанный с его помощью, становится сложно понять, если, применяя такой оператор, начать использовать вложенные конструкции:
let conferenceCost = isStudent ? hasDiscountCode ? 25 : 50 : hasDiscountCode ? 100 : 200;


let conferenceCost = (
    isStudent ? (
        hasDiscountCode ? 25 : 50
    ) : (
        hasDiscountCode ? 100 : 200
    )
);

Так понятнее? Спагетти-код можно написать на любом языке.


let eventRecord = {
  user: { name: "Ben M", email: "ben@m.com" },
  event: "logged in",
  metadata: { date: "10-10-2017" },
  id: "123"
};
let {
  user: { name: userName = "Unknown" },
  event: eventType = "Unknown Event",
  metadata: {date: eventDate}, // кто-то опечатался
  id: eventId
} = obj;


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

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


function handlePost(post) {
    const [
        {name: authorName, email: authorEmail},
        ...moreAuthors
    ] = post.authors;
    // ...
}

Этот код понятен. Вот у нас есть пост, у него есть авторы, первого мы вынесем в переменные authorName и authorEmail, остальных запихнем в moreAuthors. А теперь тот-же код без rest/spread:


function handlePost(post) {
    const authorName = post.authors[0].name;
    const authorEmail = post.authors[0].email;
    const moreAuthors = post.authors.slice(1);
    // ...
}

Сильно понятнее? Сразу поймете, почему slice(1)? Ну… нет.


Однако на практике мне удалось выяснить, что пользоваться технологией именованного экспорта предпочтительнее по следующим причинам:

Не зря сделали одновременно и default-экспорт, и именованный. default на то и default, что содержит основной экспорт. Например, если у Вас в папке хранятся плагины, экспортирующие функцию install:


import {install as installPlugin1} from "./plugin1";
import {install as installPlugin2} from "./plugin2";
import {install as installPlugin3} from "./plugin3";

Или:


import installPlugin1 from "./plugin1";
import installPlugin2 from "./plugin2";
import installPlugin3 from "./plugin3";

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


Ну и еще пример: плагины подгружаются автоматически, через require.context, но иногда нужно получить определенные плагины:


Вариант с export function install:


const context = require.context("./plugins");
for(const name of context.keys()) {
    context(name).install();
}

...

import {install as installPlugin1} from "./plugin1";
import {install as installPlugin2} from "./plugin2";
import {install as installPlugin3} from "./plugin3";

Вариант с export function installPluginN:


const context = require.context("./plugins");
for(const name of context.keys()) {
    Object.values(context(name))[0]();
}

...

import {installPlugin1} from "./plugin1";
import {installPlugin2} from "./plugin2";
import {installPlugin3} from "./plugin3";

Вариант с export default function install:


const context = require.context("./plugins");
for(const name of context.keys()) {
    context(name).default(); // Ну или как-то иначе, если будет "import ... from '*.js'"
}

...

import installPlugin1 from "./plugin1";
import installPlugin2 from "./plugin2";
import installPlugin3 from "./plugin3";



Хватит топить JavaScript. У Вас половина статей про то, какой JS плохой и половина про том, какой JS хороший. Найдите другую тему (CSS Paint — хороший пример).

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

Так понятнее? Спагетти-код можно написать на любом языке

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


Сильно понятнее? Сразу поймете, почему slice(1)? Ну… нет.

Честно? Да, раз в 5 понятнее. Просто и по делу, без ребусов. К тому же:


function handlePost(post) {
    const [firstAuthor, ...moreAuthors] = post.authors;
    const { name, email } = firstAuthor;
}

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


А целом согласен с вами по некоторым пунктам (вроде const), да и автор несколько перегнул палку.

Я вот не соглашусь с вами и соглашусь с Ivanq по поводу деструктурирующего присваивания — мне это совсем не кажется слабой стороной языка.


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


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

На самом деле, все проблемы из-за default — когда используешь require(), нужно постоянно добавлять .default. Случаев, когда нужно использовать одновременно и export default, и named export — ужасно мало. Проще было бы сделать export default = module.exports и import = require.

Ну частый случай, когда нужен и дефолт и именнованный экспорт — React+Redux приложение. Дефолтно экспортируем компонент, обёрнутый в connect(), а именнованно голым, для тестов чаще всего, но бывают и другие варианты.

Пардоньте, но с чего вы взяли, что я не люблю деструктурирующее присваивание? Я его использую почти в каждом файле по множеству раз. Одна из лучших штук в ES7. Обратите внимание на тот вариант, который я указал, как "идеальный" (по моему мнению) в комментарии выше.


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

Лично мне в деструктуризации глубоко непонятен один момент: зачем вынесли имя переменной направо?
Вот в Вашем коде:
const [
        {name: authorName, email: authorEmail},
        ...moreAuthors
    ] = post.authors;

есть строчка
        {name: authorName, email: authorEmail}
Присваиваемое: слева, имя переменной справа.

Это идёт вразрез с привычным синтаксисом объявления объектного литерала:
        {name: authorName, email: authorEmail}
   где присваиваемое: справа (очевидно, это имя переменной, чьё значение присваивается), а идентификатор, по которому впоследствии будут обращаться к этому значению: слева.

Расскажите, к чему было так корёжить привычный синтаксис и сразу ли Вы привыкли, что в одном случае код надо парсить слева направо, а в другом справа налево?

Вот затем и вынесли. Для симметричности. Смотрите:


const {name: authorName, email: authorEmail} = {name: authorName2, email: authorEmail2};
Симметричность текста приводит к асимметричности действий: в одном случае поле name означает, откуда берутся данные, в другом — куда они кладутся.
Да, приводит. Но любой другой вариант будет не менее странным.

Выход я вижу только один — не использовать такие сложные конструкции.

Ох, это, пожалуй, мне и не нравится в деструктизации. Постоянно забываю, в каком порядке идут переменные: src: dst или dst: src. Странно сделали — но, видимо, чтобы легче было парсить. Сравните:


const {
    authors: [
        {
            name: {
                full: fullName
            }
        }
    ]
} = post;

(да, это скорее пример непонятного кода, но все же)


И:


const {
    [
        {
            {
                fullName: full
            }: name
        }
    ]: authors
} = post;

Чтобы понять, что речь идет об авторах, нужно дочитать до конца, или читать снизу вверх, что еще хуже.


Сразу ли привык? Нет, до сих пор не привык полностью. Мне деструктизация больше нравится, когда имя переменной и свойства совпадают, например:


const {
    authors: [
        {fullName},
        ...moreAuthors
    ]
} = post;

Такой код смотрится элегантно.

Тернарный оператор прекрасен, если его оформлять нормально:
let i = 2;
let val = 
     i === 1 ? 'i is 1' : 
     i === 2 ? 'i is 2' : 
     i === 3 ? 'i is 3' : 
     i === 4 ? 'i is 4' : 
     '???';

В последнее время народ очень активно топит за prettier. Я не разделяю этих взглядов, но не удержался от того, чтобы посмотреть во что он превратит ваш code-style :)


let i = 2;
let val =
  i === 1
    ? "i is 1"
    : i === 2
      ? "i is 2"
      : i === 3
        ? "i is 3"
        : i === 4
          ? "i is 4"
          : "???";
Если прям очень хочется пользоваться автоформаттерами, которые портят такие выражения, то даже в таких случаях есть решение: prettier.io/docs/en/ignore.html. Ну, или более правильное — написать свой плугин для подобного форматирования тернарного оператора (как самому нравится/принято в команде).
Само наличие тернарного оператора делает скрипт более приближенным к стандартам высокоуровневых языков. Лично мне он нравится, но злоупотребоять им не советую особенно в случае большой вложенности, как в Вашем примере. Думаю всегда можно избавиться от многоэтажных условий используя другое построение алгоритма или же самих данных в сочетании с оператором switch() { case()}.
Именованный экспорт даёт возможность единообразно экспортировать из модулей всё, что угодно, в нужных количествах. Дефолтный экспорт ограничивает разработчика лишь экспортом одного значения. В качестве обходного пути тут можно применить экспорт объекта с несколькими свойствами. Однако при таком подходе теряется ценность алгоритма tree-shaking, применяемого для уменьшения размеров JS-приложений, собираемых чем-то вроде webpack. Использование исключительно модулей с именованным экспортом упрощает работу.

Субъективное мнение. Не согласен. Дефолтный экспорт дает разработчику дополнительную гибкость, например:
import {
  Config900Api,
  Other900Api,
  System900Api,
} from 'server-api';

const MyServices = {
  ConfigApi: Config900Api,
  OtherApi: Other900Api,
  SystemApi: System900Api,
};
export { MyServices, MagicIds };

Использование:
import { MyServices } from 'Api/Services';

function getAccessKeys(userId: number, taskUri?: string): Promise<SomeResponse> {
const params: SomeParams = { ... };
  return MyServices.ConfigApi.getUser(params, new Configuration(), { credentials:  { .... } })();
}

В таком случае потдержка версионности внешнего Api сводиться к изменениям в одном файле. А теперь представьте объем рефакторинга при изменении версии Api если бы вы использовали
Именованный экспорт

Извините, но статья скорее должна была называться «JavaScript ES6: слабые стороны [автора]»
У ES6 безусловно хватает слабых сторон (начать с того, что до сих пор приходится пользоваться костылями типа babel чтобы на нём писать), и ни одной из них в статье не приведено. А «проблема» const — это вообще песня…

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


Так оно и будет.

Вангую:
Если верен принцип:
Всё что можно не писать в коде (но код при этом будет работать) — будет опущено при написании кода. ©
— пример, указание типов в JavaScript — то, если можно в функциях не думать о том что использовать? (conts или let) и код будет работать(!) — то большинство будет использовать только let.
UFO just landed and posted this here
Как минимум потому что json (вы же про него?) не содержит имён классов. А даже если бы содержал, то как быть с идентичностью объектов? Если второй раз встречается объект с тем же набором полей, то новый надо создавать или только ссылку на существующий добавлять? А если в json встречается один раз, но был создан ручками до того? А если был создан до того ручками с тем же ид, но другим значением, как конфликты разруливать?
UFO just landed and posted this here
Причем, в простых случаях она и вовсе не нужна.

Наверное, это потому что Java — язык со статической типизацией. И сериализатор заранее знает типы всех полей класса. А в JS для этого не обойтись без внешних аннотаций или Typescrpit + Reflect Metadata.


А с другой стороны можно сказать, что это в Java нет "просто объектов". И нужно заводить классы чтобы просто работать со структурами данных.

Сторонние библиотеки есть и были задолго до es5 даже.
Опять «проблемы» очередного не очень умного индуса? Или вы с ним согласны, что переводите и публикуете подобные статьи?

А потом на собеседованиях куча «гениальных» разрабочиков несёт ахинею.

Вы сильно расстроились тли обиделись? У этого недоязыка и сильных то нет

UFO just landed and posted this here
нет, люблю просто потроллить тех, кто на нем пишет
Помимо того, что автор, похоже, не знает понятий «передача по ссылке» и «передача по значению» – и делает это поводом для того, чтобы ругать const – он ещё и не знает, как работают дефолтные значения в деструктурировании.

Это:

let userName = eventRecord.user.userName || 'Unknown';

ни разу не эквивалентно этому:

let { name: userName = "Unknown" } = user

Потому что первый случай вернёт 'Unknown' для любого falsy значения, а второй – только и исключительно для undefined.

Эти высосанные из пальца статьи о том, что «если писать плохой код, то код получается плохим» раздражают с каждым разом всё больше, ей-богу.
Sign up to leave a comment.