Pull to refresh
28
0
Роман Теличкин @Telichkin

✍ Software Creator

Send message

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


Тема развития определенных навыков и влияние этих навыков на все остальные аспекты жизни хорошо раскрыта в книге "Peak: Secrets from the New Science of Expertise". Автор книги является также автором исследования про 10000 часов, и в этой книге он объясняет, что популярная трактовка "10000 часов практики == профессионал" является неверной.

Как только число 3 превращают в объект, к нему гвоздями прибивают какой-то один смысл

оно ограничивает наши возможности по интерпретированию числа 3 каким-то иным образом


Вам ничего не мешает добавлять любой смысл к объекту. Если в вашей ментальной модели число может быть кодом символа, отобразите это в числе:
charCode := 3 asCharCode.
charCode + 1. "Instance of CharCode didNotUnderstand #+"


Это и отличает данные от объектов: у объекта есть «смысл»/интерпретация, пользуясь Вашей терминологией, а у данных этого нет.

Пока это число 3, это просто данные, и мы можем наделять их любым смыслом


То есть данные — это все-таки что-то, что не имеет никакого смысла? Если число «3» и строка «лето» являются данными, значит ли это, что они по-сути равны как друг другу, так и абстрактному «ничего»?
В первом сообщении я задавался вопросом, на который пока что никто не ответил :(
А что такое «данные»? Как «данные» могут существовать без интерпретатора?


Смыслом, как конкретным, так и абстрактным, и числа, и объекты наделяет человек.

Как сделать так, чтобы можно было передать смысл одного и того же объекта без искажений?
Конечно у числа есть собственный смысл, я это не отрицал. Я пытался донести идею о том, что числа — это не «данные», числа — это представление определенного смысла. А компактное представление смысла с возможностью исследовать и понимать смысл через использование доступных методов (операций, возможностей) — это объект.

Предлагаю отвлечься от программирования и подумать о том, как объяснить смысл чисел, например, дельфину.
Ребенку, который не знает чисел и основ математики, будет непонятно число 3. Для такого ребенка в числе 3 нет смысла. Он не сможет интерпретировать сообщение «3 + 2», потому что не знает смысл объектов «3» и «2» и доступных у них методов вроде "+" и "-".

Хоть вы и не ответили на мои вопросы из прошлого сообщения, я все рано задам еще. Вы действительно думаете, что числа — это что-то не имеющее смысла? А если смысл всё-таки есть, то разве методы (операции, возможности) вроде "+", "-", "*", "/" существуют не для пояснения смысла, заключенного в число?
число 3 это не объект, равно как и функция sum(a,b int) не является частью/методом какого-то объекта.


А что такое число 3? Это «данные»? А что такое «данные»? Как «данные» могут существовать без интерпретатора?

Если я напишу вам сообщение «На столе три яблока», или «На столе 3 яблока», или «На столе III яблока», скорее всего, во всех трех случая вы представите себе подобное:


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

Объект — упаковка для смысла. Методы объекта — способ понимания смысла.
"Код на Smalltalk"

tableWithThreeApples := me interpret: 'На столе три яблока'

apple := tableWithThreeApples takeOneApple.
apple isFruit. "true"

numberOfApples := tableWithThreeApple howManyApplesDoYouHave.
numberOfApples = 2. "true"


Но если интерпретаторы разные, то и объекты, а значит и смысл могут быть разными. Вполне возможен такой поворот событий:
tableWithThreeApples := appleFanboy interpret: 'На столе три яблока'

apple := tableWithThreeApples takeOneApple.
apple isFruit. "false"
apple isComputer. "true"

numberOfApples := tableWithThreeApple howManyApplesDoYouHave.
numberOfApples = 2. "true"


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

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

http get: 'microservice-1.com/apples-count'.
http get: 'microservice-2.com/apples-count'.

object1 applesCount.
object2 applesCount.

Вот здесь хорошо описано различие: https://www.stefankrause.net/wp/?p=342

В моём первом комментарии transform использовался вместо нового синтаксиса в list comprehension во всем проекте. То есть предполагалось переиспользование этой функции, а значит нужно либо вводить новую абстракцию, либо везде использовать последний вариант:

result = list(filter(filter_func, map(long_running_func, data)))


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

Жаль, в python у коллекций нет методов map и filter, чтобы пример выше выглядел читабельнее:

result = data.map(long_running_func).filter(filter_func)


Адекватный разработчик узнает ограничения, в которых было написано это решение. Здесь transform решает проблему вызова долгой функции два раза. Если у адекватного разработчика будет более симпатичный вариант решения, то он его предложит, а не будет заворачивать ревью.

Почему бы не отрефакторить и сделать «Без», но линейно и прозрачно?
reductor = dispatch_table.get(cls)
if reductor:
    return reductor(x)

reductor = getattr(x, "__reduce_ex__", None)
if reductor:
    return reductor(4)

reductor = getattr(x, "__reduce__", None)
if reductor:
    return reductor()

raise Error("un(shallow)copyable object of type %s" % cls)


Сразу видно повторение структуры
reductor = some_get_reductor_strategy()
if reductor:
    return reductor(something_or_nothing)


Которую частично можно заменить nullable object'ом
def dummy_reductor(*args): pass

result = dispatch_table.get(cls, dummy_reductor)(x)
if result:
    return result

result = getattr(x, "__reduce_ex__", dummy_reductor)(4)
if result:
    return result

result = getattr(x, "__reduce__", dummy_reductor)()
if result:
    return result

raise Error("un(shallow)copyable object of type %s" % cls)


И избавиться от повторяющихся if'ов
def dummy_reductor(*args): pass

result = (
    dispatch_table.get(cls, dummy_reductor)(x) or
    getattr(x, '__reduce_ex__', dummy_reductor)(4) or
    getattr(x, '__reduce__', dummy_reductor)()
)

assert result, 'un(shallow)copyable object of type %s' % cls
return result 


Уверен, что код можно еще упростить, если знать контекст.

В list comprehension новая фича выглядит полезной, пока не приходит осознание, что при реальной необходимости её можно реализовать самостоятельно один раз на весь проект:

def transform(iterable, with_transform, and_filter):
    result = []
    for item in iterable:
        item = with_transform(item)
        if and_filter(item):
            result.append(item)
    return result

result = transform(
    data, 
    with_transform=long_running_func, 
    and_filter=lambda x: x is not None
)

result = transform(data, long_running_func, lambda x: x is not None)

Представьте, что вы воспользовались сервисом по уборке квартиры. Они пришли, начали уборку, но, по ходу дела, всю мебель в квартире переставили и шторы из гостинной в ванну перевесили с аргументацией "в таких условиях уборщице было приятнее выполнять свою работу". Картинку с "WTF?!" можете домыслить сами.

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


И просто хорошая цитата про рефакторинг:


for each desired change, make the change easy (warning: this may be hard), then make the easy change
— Kent Beck (@KentBeck) 25 сентября 2012 г.


Вместо Object.setPrototypeOf(self, q) можно использовать Object.assign({}, q, self).

Невозможно наследовать.

Вам действительно нужно наследование? Если без него действительно никак не обойтись, то можно реализовать вот так:


function LimitQueue(limit) {
  const q = Queue();
  const self = {
    enqueue(elem) { q.size() < limit && q.enqueue(elem); }
  };
  return Object.setPrototypeOf(self, q);
}

function Queue() {
  const state = [];
  return {
    enqueue(elem) { state.unshift(elem); },
    dequeue() { return state.pop(); },
    size() { return state.length; }, 
  };
}

Невозможно мокнуть куски для тестирования.

Из-за "мокнутых кусков" тестирование превращается в ад. В данном случае, если очень хочется, можно создать фэйковый Queue и использовать его в тестах:


function someTest() {
  let calledTimes = 0;
  const fakeQueue = { enqueue(elem) { calledTimes += 1; } };

  objectUnderTest.someMethod(fakeQueue);

  assertEqual(1, calledTimes);
}

Конкретно этот пример надуманный, но суть он показывает.


При разростании это ведёт к такому

А зачем разрастаться, если объекты можно делать маленькими и разносить по разным файлам?


// LimitQueue.js
import Queue from './Queue';

export default function LimitQueue(limit) {
  const q = Queue();
  const self = {
    enqueue(elem) { q.size() < limit && q.enqueue(elem); }
  };
  return Object.setPrototypeOf(self, q);
}

// Queue.js
export default function Queue() {
  const state = [];
  return {
    enqueue(elem) { state.unshift(elem); },
    dequeue() { return state.pop(); },
    size() { return state.length; }, 
  };
}

Как думаете стоит ли написать полноценную статью с критикой такого подхода

Напишите, мне будет интересно почитать.


Но имхо это намеренная потеря гибкости не понятно зачем.

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


JS прекрасный ОО-язык, но его зачем-то превращают в еще одну Java, а рынок, к сожалению, этот тренд поддерживает.

Когда мы размышляем об объектах, то первое, что приходит в голову — это классы.

Видимо, зря придумали ключевое слово class, оно затмевает сознание и мешает думать об объектах. В JS можно создавать объекты вообще без this, new и class, а только с помощью фабрик, замыканий и, собственно, объектов:


function Queue() {
  const store = [];
  return {
    enqueue(elem) { store.unshift(elem); },
    dequeue() { return store.pop(); },
  }
}

Чем такой способ создания объектов хуже классов?

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

Если код приходится "покрывать" тестами, лучше забыть про Unit-тесты, иначе и в тестировании разочаруетесь, и руководителям нервы испортите. Напишите интеграционные или приемочные тесты. Главное, чтобы они позволили без страха рефакторить и добавлять новый функционал. А новый код пишите по TDD.


Большие куски кода сложно ревьюить.

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


Быть в контексте задачи помогает парное программирование, но продать его руководству очень сложно.


Переиспользуйте код, выделяйте абстракции, не стесняйтесь.

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


Чтобы писать меньше кода, нужно:


  • перестать реализовывать бесполезные фичи
  • решать с помощью кода только текущие проблемы, а не мечтать "когда мы будем как Facebook, такое простое решение работать не будет"
  • сначала написать падающий тест
  • потом заставить тест проходить
  • закончить рефакторингом
  • если рефакторинг не идет — оставьте код в покое, позже все равно поймете как сделать лучше

Рассматривали ли вы вариант использования монорепозитория? Сразу уберет описанные вами боли, так как в dev-ветке модули, которые не изменялись, будут current-версии.

CI, Agile, DevOps имеют между собой одну общую черту — их убил маркетинг. Теперь CI — это не способ улучшения коммуникации в командах разработки, а GitlabCI, Jenkins, Travis и прочие инструменты. Agile и DevOps — не набор ценностей и принципов, а Kanban, Scrum, Daily meeting, Docker и другие buzzwords.


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

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

А что не так с декораторами и console? :)


// log.ts

import { toJS } from "mobx";

export default function(target: any, methodName: string, descriptor: TypedPropertyDescriptor<any>) {
    const originalMethod = descriptor.value

    descriptor.value = function(...args: any[]) {
        console.log(target.constructor.name);
        console.log("|  Previous State:", toJS(this));
        console.log("|  Method:", methodName);
        console.log("|  Arguments:", ...args);
        const returnValue = originalMethod.apply(this, ...args);
        console.log("|  Next State:", toJS(this));
        console.log("\n");        
        return returnValue;
    }

    return descriptor;
}

// Counter.ts

import { observable, action } from "mobx";
import log from "./log";

export default class Counter {
    @observable count = 0;

    @action.bound @log increase() { this.count += 1; }

    @action.bound @log decrease() { this.count -= 1; }
}

// CounterView.tsx

@observer
export default class extends React.Component<{ counter: Counter }, {}> {
    render() {
        const counter = this.props.counter;

        return (
            <div>
                <button onClick={counter.decrease}>{"-"}</button>
                <span style={{ padding: "20px" }}>{counter.count}</span>
                <button onClick={counter.increase}>{"+"}</button>
            </div>
        )
    }
}

Information

Rating
Does not participate
Location
Санкт-Петербург, Санкт-Петербург и область, Россия
Registered
Activity