Badoo corporate blog
Programming
Go
Comments 85
+3
>После того как я поменял последнюю строчку на record := fmt.Sprint(size) + " " + k + "=" + v + "\n", я увидел значительное ускорение

… И существенно менее читаемый код. В модуль не смотрел, но если это работает в цикле и вызывается часто — быстродействие важнее, но если 1-2 раза на упаковку — это плохой, вредный код. Ну или хотя бы надо оставить старый код как комментарий, для понимания.
+2
Ну или хотя бы надо оставить старый код как комментарий, для понимания.

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

+9
length := len(b)
if b == nil {

это теперь нормально — сначала использовать аргумент, а потом проверять его на пустое значение?
-2
len(nil) в go возвращает ноль. Так записано для читаемости.
Собственно это и хотел исправить автор вначале =)
+16
len(nil) в go возвращает ноль


и это сделал человек, утверждавший, что «errors are values»?
-3
Это сделали в языке, где нулевое значение максимально используется на полную катушку. Это важная часть языка. Поэтому nil массив это полезное значение, а не источник ошибок и постоянной необходимости проверок на nil. Конкретно код len(nil) не скомпилируется, а вот если в него передать переменную типа массив со значением nil, то действительно вернется 0. А еще при этом будет работать тот же cap() и append(). В итоге, и код чистый, и безопасностью никто не пожертвовал.
+1
Конкретно код len(nil) не скомпилируется, а вот если в него передать переменную типа массив со значением nil, то действительно вернется 0.

Чтобы уточнить,


arr := nil
len(arr)

соберётся, а


len(nil)

нет?

+1
Ни первое, ни второе не соберется — необходимо указать тип (use of untyped nil):

func main() {
	var arr []int = nil
	print(len(arr))
}


Второе — по аналогии можно собрать:

func main() {
	print(len([]int(nil)))
}

+5

Я Go-то не знаю, это скорее псевдокод был. Спасибо за пояснение!


Но вообще это, конечно, грустно.

+13
len(nil) в go возвращает ноль. Так записано для читаемости.

Сложно назвать это читаемым. Говнокод как говнокод.
+3

Я бы немного уточнил.
Магия тут не в len() — len всегда делает одно и то-же: берет поле Len из слайса, который представляет из себя структуру вида.


type Slice struct {
    Data uintptr
    Len  int
    Cap  int
}

Магия тут в "касте" nil в слайс ( при сравнение или присвоении ). Сравнение слайса с nil — это сравнение со Slice{0,0,0} что является значением по умолчанию при иницализации переменной слайса. В результате — только-что созданный слайс == nil


Зачем так было сделано? Как уже было написано — возможность работать со слайсом без дополнительных проверок. Это из того-же порядка что и отсутсвие необходимости инициализировать переменную типа bool.

+17

То есть считается, что код вида


length := len(b)
if b == nil {
    // No byte slice present. Assume string s should be encoded.
    length = len(s)
}

менее понятный, чем


if b == nil {
    length := len(s)
} else {
    length := len(b)
}

Кажется, я был прав когда не стал изучать этот язык...

+11

Во втором примере вы на самом деле объявляете две разные переменные length, каждая в своей области {...}. Использовать их за пределами этой области не получится, там они не будут существовать. Такой код не скомпилится, потому что в Go нельзя объявлять локальную переменную и не использовать ее. Поэтому придется писать так:


var length int // Объявление
if b == nil {
    length = len(s)
} else {
    length = len(b)
}
// Используем length

Это значительно длинее первого варианта, хотя и понятнее.

+5

Спасибо за исправление.


Это значительно длинее первого варианта, хотя и понятнее.

Ну так это же практически девиз языка go — "длиннее и понятнее". Но, видимо, даже у авторов стандартной библиотеки нету сил следовать ему полностью...

+8
Это значительно длинее первого варианта, хотя и понятнее.

В любом адекватном языке:
length := (b == nil) ? len(s) : len(b)

Или, даже, так:
length := len(b == nil ? s : b)

А иногда и так::
length := len(b || s)


И коротко, и понятно. Но в гоу все через жопу.
-10
И все ваши примеры сложнее и менее читаемы, чем оригинал.
+7
Вы про тот оригинал, где берется длина nil, она оказывается равной 0 вместо ошибки, присваивается переменной, а потом внезапно передумывается и присваивается длина чего-то другого, да? Читаемее? Проще? Может еще и логичнее? Не смешите моего кота!

В то время как len(b || s) — читается очень просто: дай длину b (если существует) или s
0

Можно я с вами поспорю? :) Я вот, не зная этой конструкции, прочитал "дай длину b или s… ну чего-нибудь из этих двоих, всё равно чего". Нет, серьёзно, вот этого "если существует" в этой записи нигде нет, руку на сердце положа. Это такая же особенность языка как length(nil) в Go, которую тоже надо знать, просто она для вас привычнее. Гхм, ну и length(nil) тоже в принципе логику некую имеет — чему равна длина чего-то несуществующего? Ничему, нулю. (Если что, я не пишу ни на Go, ни на JS, просто погулять вышел.)

+3
А если это специальный оператор, у которого только такая задача?

Дело в том, что я знаю такую особенность Гоу (я на нем писал последнее время) и мне она кажется крайне нелогичной — это костыль в спецификации, потому что нету удобного инструмента.

чему равна длина чего-то несуществующего?

Ошибке (потому что в Гоу обычно возвращается ошибка в таких нелогичных случаях).

Но проблема ведь не только в len(nil), но и в том, что сперва ты присваиваешь неправильное значение, а потом проверяешь условия и присваиваешь правильное! Бред ведь.

Еще раз, я писал на Гоу, мне привычный этот синтаксис и я все-равно считаю его ущербным.
0

Возможно, в каком-то языке это такой специальный оператор, у которого именно такая задача. Возможно, в другом языке это вообще бы значило "дай длину true если b — true или a — true, или длину false если оба false" — и, нет, я не могу ответить, какой физический смысл у len(true). :) Хорошо, для тех, кто пишет на C#, этот код читается просто. Откуда следует, что он универсально простой для всех? Для его понимания как минимум надо знать особенность конкретного языка, вот я про что.


Про длину несуществующего — я говорю не с точки зрения соответствия вообще конвенциям языка (я так глубоко в Го не лез, не моя епархия), а просто с точки зрения логики, не привязанной к какому-то языку. У ничего и длина ничего.


Да, если что — я ни разу не защищаю код из статьи, присвоение а потом проверка (для меня, но я не пишу на Го) вызывает содрогание. Но вот для примера — если бы там дальше стоял цикл (гипотетически, в какой-нибудь другой функции) и он бы при b = nil корректно отрабатывал 0 раз (поскольку len(nil) = 0) и функция возвращала бы пустую коллекцию, то это могло бы читаться нормально. По Мартину прямо. Так что я не думаю, что len(nil) = 0 это так уж нелогично. Код из статьи — ну, да. Но если я скажу "но логичнее было бы возвращать ошибку" или "логичнее было бы выбрасывать исключение", то я просто буду примерять ко всему набор вывихов, которые у меня в мозгу для другого языка, и не факт, что они на самом деле логичнее, просто я к ним привык.

+5
Так в том же и проблема — это кривой код как с точки зрения Гоу, так и с точки зрения computer science в целом.

И если второе очевидно, то первое объясню — обычно (та почти всегда, наверное) Гоу не позволяет делать таких вещей и выбрасывает ошибку. Та он выбрасывает ошибку даже когда ее можно не выбрасывать, просто чтобы выбросить.

Временно закомментил код, но не закомментил импорт — ошибка «imported and not used». Блин, я же дебажу, я его потом разкоменчу! Но нет, го--- оправдывает свое название и вместо того, чтобы написать ворнинг — падает в панику
package main

import "fmt"

func main() {
	// fmt.Println("Hello, playground")
}


Разрабатываешь, пишешь логику, заранее объявил переменные, чтобы потом заюзать, решил проверить, как оно работает часть, которую написал, запускаешь. Фиг там — паника «declared and not used».
package main

func main() {
	var arr []int = nil
}


Ну то есть этот код никак не влияет на производительность, компилятор знает, что он лишний (все-равно провел анализ) и может быть спокойно выброшен, но нет ведь — ошибка

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

Я уж молчу о тошнотворном повторении как мантры if err != nil (это настолько въедается, что ты просто раз за разом копипастишь, как в анекдоте про студента и гвозди). Раз гвоздь, два гвоздь, но у тебя уже их в заднице столько, что ты привык:
a, err := doA()
if err != nil {
  return nil, err
}
b, err := doB(a)
if err != nil {
  return nil, err
}
c, err := doC(b)
if err != nil {
  return nil, err
}


Видите, как Go относится к таким ошибкам? Это и есть Go-way. И гоу-штунды кричат: «это не так плохо», «это даже клево», ведь это очевидно, ты должен контролировать свой код, ты должен писать свою архитектуру, ошибка должна быть явной, рекурсивная зависимость — плохой код, срочно рефакторите.

Но последовательность им не знакома, потому, на говнокод они кричат: «зато так покороче», ну и что, что он nil воспринимает как пустой массив, зато так удобно, нет ничего страшного, что я сначала присваиваю переменной неправильное значения, вызывая функцию len, когда ее не надо вызывать!

Зато неиспользуемые import'ы использовать нельзя — ведь Гоу следит за чистотой кода, видите ли.
0
… это кривой код как с точки зрения Гоу, так и с точки зрения computer science в целом.

Это про len(nil), или именно про код из статьи? Если про код — да, я в принципе не спорю, можно было создать переменную без лишнего вызова len() с потенциальным мусором в аргументе. Я только про то, что контракт len(nil) -> 0 в принципе не криминал и может пригодиться в какой-нибудь другой ситуации.


С неиспользуемыми переменными и импортами да, тоже перегиб. Согласен — иногда хочется что-то набросать и проверить, или выкусить фрагмент на время. (Вроде goimports хотя бы об импортах позволяет забыть, но это если IDE.)


А что видится альтернативой цепочкам if-ов с проверками err? Исключения? Вообще — я отчасти понимаю, почему их нет. Язык бедный, он просто намеренно бедный — ему вроде бы как раз в заслугу ставят то, что команда вчерашних студентов может через неделю выдавать более или менее удобоваримый код, а не сидеть с опухшими головами и отвлекать сеньоров вопросами. Исключения всё-таки предполагают осознание того, что у нас в коде куча неявных точек возврата, кривая обучения сразу круче становится. (Если что, я за выразительные языки. Просто философски отношусь к тому, что рынок хочет толпу годных в дело стажёров.)

+2
А что видится альтернативой цепочкам if-ов с проверками err? Исключения?

Монадическая обработка ошибок на манер Either.

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

Она в любом случае не может. Какие такие задачи они будут решать? Люди не спотыкаются, о синтаксис языка, если это только не какой-то haskell, люди страдают от сложности библиотек и фреймоворков, но тут то go даже хуже, чем другие языки, потому что выразительные конструкции заменяются на структуры и кучи шаблонного кода.

0

Я не уверен, что это корректный аргумент. Вроде есть отзывы о том, что новички очень быстро становится продуктивным на Го (на бэкенде во всяком случае). И с другой стороны, выразительные конструкции могут, наоборот, усложнить понимание фреймворков и библиотек. Регулярно слышу, как народ стонет по поводу библиотек на Scala, которые без автора сопровождать невозможно.

0
Люди не спотыкаются, о синтаксис языка, если это только не какой-то haskell

В хаскеле, кстати, предельно простой синтаксис. Не лисп, конечно, но всё же.

+2
Я уж молчу о тошнотворном повторении как мантры if err != nil

Я в каком-то соседнем треде уже предлагал, но предложу ещё раз: давайте замутим стартап по продаже USB-донглов с кнопкой, которая по нажатию выдаёт if err != nil { return nil, err }.

+1
a, err := doA()
if err != nil {
  return nil, err
}
b, err := doB(a)
if err != nil {
  return nil, err
}
c, err := doC(b)
if err != nil {
  return nil, err
}


интересно, а как при этом принято логировать такие ошибки?
+1
т.е. строчек по обработке ошибок должно бы быть больше, и смысловые строчки реально станут настолько редкими, что будет трудно со стороны отследить корректность алгоритма? или есть какой-то способ обойти проблему?
0

Я не говорю, что так надо делать, я просто сделал так для простоты, когда изучал Го

+1
А так:
let length = if let Some(s) = s {s.len()} else {d.len()};
или так:
let length = s.unwrap_or(d).len();
?
0
Да, такой вариант мне тоже всегда нравился. Но в силу вынужденных компромиссов приходится довольствоваться теми, что выше.
+1
Вы можете составить список языков программирования, фреймворков и фильмов, которые считаете негодными? Я чувствую, там много хорошего.
-4

Языки программирования: Ada, Go и Papyrus Script
Фреймворки: Angular, redux*


* да, я знаю что это библиотека, а не фреймворк. Но к библиотеке у меня претензий как раз и нет.

-1
конкатенация строк гораздо быстрее, чем fmt.Sprintf().
После того как я поменял последнюю строчку на record := fmt.Sprint(size) + " " + k + "=" + v + "\n", я увидел значительное ускорение

Почему компилятор сам этого не делает?
+1

Почему компилятор не знает всё про внутреннюю логику и побочные эффекты какой-то функции какого-то пакета и не заменяет её вызов на вызов другой функции? Ну, что Вам сказать… придётся признать, что разработчики компилятора невероятно ленивы и толком не умеют писать компиляторы.


А если серьёзно, то, как уже упоминали выше, такого рода оптимизации во-первых зачастую преждевременные, во-вторых ухудшают читабельность, в-третьих в следующих версиях языка и/или пакета fmt более медленный вариант сейчас может оказаться более быстрым, в-четвёртых если уж очень хочется, то рекомендации по такого рода оптимизациям должны давать внешние утилиты вроде статических анализаторов кода (линтеров) или IDE.

+1
Почему компилятор не знает всё про внутреннюю логику и побочные эффекты какой-то функции какого-то пакета и не заменяет её вызов на вызов другой функции?

Да не, почему. Авторы функции, предполагая, что sprintf используется часто, и вложиться в производительность имеет смысл, могли бы написать правила по компил-тайм-парсингу строки формата во что надо, получив требуемый результат. От языка требуется только предоставить доступ к AST, все такие и подобные случаи покроются с лихвой.


А, в Go так нельзя? Ну сорян.

-1

А часто ли? Я вот просто не уверен, что стоит огород городить для очень частного случая. Если у горстки человек sprintf() внезапно окажется одной из самых долгих вещей на горячем пути выполнения — ну, наверное они этот путь всё равно будут профилировать, и сделают то же, что сделал автор оригинальной статьи. А просто так, чтобы было… не знаю. Да даже подвиг автора взять — скажем, взять большущий архив, миллион файлов, по… скажем, пять записей в расширенном pax-заголовке для каждого из файлов. Двести наносекунд экономии на запись, и получится экономия аж в одну секунду на создание архива. Кошкины слёзы, а читаемость кода ухудшилась.

-2
Как бы мне этого ни хотелось, бюджет у go-team не бесконечный — все фичи не реализуешь. Я думаю, если напишете такой оптимизатор, вам только спасибо скажут.
0

Это не оптимизатор, это некоторое свойство языка, позволяющее программе на этом языке, опять же, иметь доступ к AST и всяко им манипулировать на этапе компиляции.

-2
Да-да, я об этом. Допишите компилятор. Это ценнее тысячи слов :)
0

Так не пропустят же. Девиз go — "никакой магии", а предлагаемая фича — именно что магия, притом самая сильная из известных.

0
а есть примеры кода как это выглядит в реальной жизни (я так понимаю речь про haskel)?
+2

Конкретно примеры со sprintf, чтобы с модификаторами форматирования и всем таким, мне неизвестны. Однако, вполне возможно написать библиотеку (заняться, что ли?), которая бы использовалась примерно так:


import MyPrintf

fmtSomeArgs :: Int -> String -> Double -> String
fmtSomeArgs num str anotherNum = [p|%d %s %4.2f|] num str anotherNum

с парсингом строки формата на этапе компиляции, проверкой типов и прочими ништяками.


Если упрощать, вот эта вот конструкция [ident|...|] говорит компилятору, что надо передать функции ident (которая является специальной хреновиной — квазиквотатором) содержимое между скобок и практически синтаксически вставить в точку вызова результат этой функции.


Как пример того, что уже есть — есть куча библиотек для интерполяции, вроде такого:


import Data.String.Interpolate

data Person = { name :: String, age :: Int, income :: Double }

fmtPerson Person { .. } = [i|This is #{name}, #{age} years old, their income is #{income} and they #{if income > 100000 then "are" else "are not"} rich|]

Аналогично делаются raw string literals. Есть библиотека для вызова шелла и сторонних процессов:


import System.Command.QQ

copySomeStuff = do
    exitCode <- [sh|scp #{remoteHost}:#{remotePath} #{localPath}|]
    guard $ exitCode == ExitSuccess
    sha1 <- [sh|sha1sum #{localPath}|]
    putStrLn sha1

Тут, кстати, и прелести системы типов видно — sh умеет определять по контексту, хотят от него код возврата, stdout или что-то ещё.


Есть библиотека для порождения типов dbus-функций:


import DBus.QuasiQuoter

[dbus| s -> s a{uv} |]

опишет соответствующий тип хаскелевской функции.


Можно этой ерундой парсить XML в компил-тайме, можно генерировать XML, можно вообще много чего делать. Самая прелесть — ничего этого не надо держать в ядре языка, всё это можно делать библиотеками.

0
здесь в общем случае нет, так как компилятор сам посчитает длинну и выделит сразу память под конечную строку
0
это точно? что то я сомневаюсь, компилятор ничего не знает о переменных что будут в рантайме, как он может сразу под конечную строку выделить память?
+4

Лучше было бы создать функцию вычисления длины (пишу в псевдокоде):


int getLength(string b, s)
{
  if (b != null)
    return len(b);

  return len(s);

  // return b != null ? len(b) : len(s);
}

А затем в основной функции вызвать ее так:


length = getLength(b, s);

Что получаем:


  1. Код основной функции становится чище — в нем показывается, что нужно делать, а не как делать.
    Во вспомогательной функции последовательно проверяем "особые" условия (которые теоретически могут добавляться), каждый раз return-имся, в конце возвращаем основное значение.


  2. Да, избавляемся от действительно не очень красивого "else" в коде основной функции.


  3. И самое главное — избавляемся от того, что код, приведенный в начальном варианте:


    length := len(b)
    if b == nil {
    // No byte slice present. Assume string s should be encoded.
    length = len(s)
    }

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


  4. А на возражение, что вызов функции — накладные расходы, ответить очень просто: а вот пусть в этом случае компилятор показывает чудеса оптимизации и инлайнит вызов этой функции.
    Если вспомогательная функция находится в том же модуле/пакете/сборке, и не видна наружу (internal), то ее вполне можно заинлайнить по умолчанию; хотя можно и помочь компилятору, указав соответствующий атрибут, если таковой есть в языке/платформе.
-7
С вашим подходом не получится хейтить go абсолютно без каких-либо на то оснований. Там выше уже много гигантов программизма, которым достаточно довода «а мне прост не нра», чтобы язык назвать плохим.
+2

Интересно, а какие доводы в пользу неудачного синтаксиса вы ожидаете? Исследований, которые скажут, что да, вот такой синтаксис приводит к 100к ошибкам в месяц?

+1
Объективно было бы сравнить с другими языками, противопоставить одной точке зрения другую. Но как можно ожидать объективности от людей, которые как ошалелые минусуют комментарий, в котором констатируется факт: областей видимости в привычном виде в go нет? И побежали минус-минус-минус. Часто слышу от молодняка слово «совок». Ну так я в данный момент в нем. Вот он — совок-то.
+2

В привычном — это в каком? У каждого языка эта "привычность" довольно своя. Вот у python там один ад, в javа вот там все понятно.

-1
Хорошо, в явном. В таком, где нужно было бы писать private, public etc. В go области видимости — часть синтаксиса. Иногда это бесит. Но если поумерить свое эго, то нормально.
0

private, public ect — это модификаторы доступа. Они не имеют отношения к области видимости.
Ну, так в языках, с которыми я знаком (Java, C#), где-то по другому?

-5
В go, кстати, нет областей видимости, которые надо явно указывать. Если функция или метод структуры начинается со строчной буквы — она не видна извне. А если с заглавной — то видна.
-4
Вот когда этих условий станет слишком много, тогда и стоит выделять в функцию. У вас видно больше какое-то маниакальное стремление выделить ерунду в отдельную функцию, тем самым ухудшив читаемость. И не только потому, что эти несчастные несколько строк надо искать по коду. Как раз название getLength абсолютно не отражает сути того, что же сделает функция, да еще и написали вы ее неправильно — изначальный вариант содержит аргументы b и s разных типов. Какую длину, чего она вернет — сумму длин? Одной из двух? Если одну из двух, то по какому условию?
+3

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


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


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


Но вы почему-то написали несколько придирок не по существу к моим тезисам, и проигнорировали основное — что речь шла о возможных способах замены этого кода:


length := len(b)
if b == nil {
    // No byte slice present. Assume string s should be encoded.
    length = len(s)
}

Это же никуда не годится.
Во-первых, нужно помнить, что первая строчка не упадет с исключением.
Точнее, в случае Go — что функция len имеет декларацию "func len(v Type) int", а не "func len(v Type) (int, error)".
Но этот код может читать и неподготовленный человек.


Во-вторых, это же хак — рассчитывать на то, что компилятор транслирует этот исходный код в бинарный так, как будто он написан через else.
Нечитаемо, и кроме того, поведение компилятора ведь может и измениться.

-3
Сплошные «может быть» и «наверное». Мы вынуждены поверить, что написать так — это правильно просто потому, что «может быть». А «может быть» это ваше существует только потому, что аргументов нет. Есть аргумент — код стал короче. В данном конкретном случае короче он стал из-за упрощения конструкции. Но мы, конечно, должны исходить из того, что компилятор «может быть» такой код воспримет хуже. И конечно, варианта, что такой код он воспримет лучше, быть не может. Все правильно.

В 1933 году Петр Борисович Ганнушкин ввел термин "салонное слабоумие". Символично, но я готов дополнить его теорию. Те, кто не хочет признавать истину авторитетов должны быть такими вот салонно слабоумными изгнаны.
+3
В 1933 году Петр Борисович Ганнушкин ввел термин "салонное слабоумие". Символично, но я готов дополнить его теорию. Те, кто не хочет признавать истину авторитетов должны быть такими вот салонно слабоумными изгнаны.

Звучит иронично, потому что 90% golang построено на авторитетный решениях, с которыми далеко не все согласны.

-3
Правильно! И они что делают? Аргументируют. Может быть, я ошибаюсь, но как-то не припомню, чтобы те или иные решения были выкачены просто потому. Обычно аргументация присутствует. Либо говорят «мы считаем, что так и так — это правильно». Ок, я услышал — это точка зрения.

Пример — отсутствие исключений. Долгий был срач на эту тему, но аргументация от разработчиков была. Разве нет? Если бы они заявили «исключений не будет потому, что они не нужны» без объяснения, почему не нужны или с неубедительной аргументацией, я бы так же точно ополчился на них.

Но вообще-то надо отвечать за себя. Причем тут ваш комментарий?
+2
Аргументируют.

Или делают вид, что аргументируют. С одной стороны "у нас логичный и строгий язык", а с другой "len(nil) == 0". С одной стороны "исключения плохо, потому что везде приходится фигашить проверки", а по факту стало еще хуже, так как вместо типизированных исключений мы получаем переменную err, которую так же надо обрабатывать, но она менее прозрачная. Шаблонные типы не нужны, потому что это "усложняет язык", а вот кодогенерация — нет.

-2
С третьей стороны, когда анонсировали 2.0 (или рассуждали о том, каким он будет), я своими глазами помню как они написали что-то типа «да, мы говорили, что дженерики — гумно, но если надо, то давайте сразу подумаем, чего не хватает, чтобы это заранее заложить».
-2
Можно вас и вообще всех попросить слить мне карму окончательно, чтобы каждый раз, когда у меня возникнет желание что-то прокомментировать, я сразу же это видел и бросал это дело? Спасибо.
-4
Прошу прощения. Я тогда дополню, чтобы лучше сливалось. Хуесосы. Вы все. Администрация — тоже. Я так не думаю, но мне поднадоело. Я знаю, что если мне не слить карму, я рано или поздно приду к мысли «да ладно, напишу комментик».
+2
а вот пусть в этом случае компилятор показывает чудеса оптимизации и инлайнит вызов этой функции.
Говорят, что функции «инлайнятся», но не слишком хорошо: только те, что не вызывают других функций.
+1

По-моему, ваш первый пул-реквест нужно было принять (почему — написал выше), а вот второй как раз отклонить:


  • Конкатенация строк вместо форматирования очень сильно снижает читаемость кода.
  • Что касается снижения производительности в случае использования форматирования строк, то представляется, что функция, которая внутри себя форматирует строки, явно не предназначена для вызова в цикле из миллиона итераций.
    А в случае однократного (или, пусть несколько десятков) вызова — как раз тот случай, когда можно пожертвовать производительностью даже в несколько раз ради читаемости.
    Кроме того, если эта функция форматирует строки, вряд ли она предназначена для вызова рядом функциями, быстро вычисляющими какую-нибудь математику. Скорее всего, эта функция вызывает в контексте каких-то еще более медленных функций.
0
size := len(k) + len(v) + padding
size += len(strconv.Itoa(size))
record := fmt.Sprintf("%d %s=%s\n", size, k, v)

а что будет, если len(k) + len(v) + padding = 99 ??
0
будет баг нет?
как я понял size так же учитывает и свою собственную длину в строковом представлении
+2
точно, в глянул в оригинале, там повторное кодирование строки, если длина не та
это ж пипец

// formatPAXRecord formats a single PAX record, prefixing it with the
// appropriate length.
func formatPAXRecord(k, v string) string {
	const padding = 3 // Extra padding for ' ', '=', and '\n'
	size := len(k) + len(v) + padding
	size += len(strconv.Itoa(size))
	record := fmt.Sprintf("%d %s=%s\n", size, k, v)

	// Final adjustment if adding size field increased the record size.
	if len(record) != size {
		size = len(record)
		record = fmt.Sprintf("%d %s=%s\n", size, k, v)
	}
	return record
}


ps
формат PAX record весьма неудачен, имха

pps
CL автора такие не принят
+1
ну его поправили чутка и приняли: github.com/golang/go/commit/23cd87eb0a2d49a3208824feaf34d8b852da422f

ну
func formatPAXRecord(k, v string) (string, error) {
	const padding = 3 // Extra padding for ' ', '=', and '\n'
	size := len(k) + len(v) + padding
	sizeLen := len(strconv.Itoa(size))
	size += sizeLen
	sizeStr := strconv.Itoa(size)
	if len(sizeStr) != sizeLen {
		sizeStr = strconv.Itoa(size + len(sizeStr) - sizeLen)
	}

	record := sizeStr + " " + k + "=" + v + "\n"

	return record, nil
}

не знаю насколько читаем этот вариант, но для начального кода:
BenchmarkFib10-8 10000000 163 ns/op 32 B/op 2 allocs/op
для этого
BenchmarkFib10-8 20000000 94.9 ns/op 16 B/op 1 allocs/op
тоже чтоли пул риквест сделать
0
хотел придраться к тому что strconv.Itoa(size) вызывается дважды, но бенч показывает одну алокацию ))
0
ну size то меняется между ними поэтому тут одной никак
по поводу аллока — это то что выделяется в куче, то что выделяется на стеке там не показывается (ибо быстро и так легко не отследишь)
кстати это на 1.9 были результаты ради интереса проверил на 1.8:
BenchmarkFib10-8 5000000 247 ns/op 32 B/op 5 allocs/op
BenchmarkFib10-8 10000000 181 ns/op 16 B/op 4 allocs/op

получается неплохо так доработали компилятор в 1.9 — как миниму он сумел все вызовы strconv.Itoa на стеке разместить
0
была мысль что заинлайнил вызовы strconv.Itoa и разместил результат на стеке
отличный результат на 1.9 надо переходить
0

там целом повторяется строка


strconv.Itoa(size) + " " + k + "=" + v + "\n"

как и исходная


fmt.Sprintf("%d %s=%s\n", size, k, v)
+3
Странная логика у автора, вместо того чтобы попытаться оптимизировать работу библиотечной функции fmt.Sprintf() он заменяет ее на черти что, и называет это оптимизацией.
Only those users with full accounts are able to leave comments. , please.