Как не надо писать факториал в Java

Original author: William Woody
  • Translation
Перевод этой статьи уже был однажды опубликован на хабре, но там почему-то осталась за кадром самая важная часть. Ниже — полный перевод.

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

Если вам надо написать факториал на Java, то большинство из ваc, вероятно, начнёт с чего-нибудь такого:
    public static int factorial(int n)
    {
        if (n == 0) return 1;
        return n * factorial(n-1);
    }


Завернём это в класс (мы всё-таки говорим о Java), вероятно, это будет какой-нибудь вспомогательный (*Util) класс, например:
public class FactorialUtil
{
    public static int factorial(int n)
    {
        if (n == 0) return 1;
        return n * factorial(n-1);
    }
}


Просто, разве нет?

Существует и нерекурсивное решение:
public class FactorialUtil
{
    public static int factorial(int n)
    {
        int ret = 1;
        for (int i = 1; i <= n; ++i) ret *= i;
        return ret;
    }
}


Внимательный читатель заметит, что результат может быть больше, чем максимально допустимое целое число (тип integer), и наверняка захочет переписать функцию так, чтобы она использовала BigInteger или хотя бы long, в зависимости от требований к программе. Итак,
public class FactorialUtil
{
    public static BigInteger factorial(int n)
    {
        BigInteger ret = BigInteger.ONE;
        for (int i = 1; i <= n; ++i) ret = ret.multiply(BigInteger.valueOf(i));
        return ret;
    }
}


Обратите внимание, что до сих пор я не использовал тот факт, что я постоянно высчитываю одни и те же промежуточные значения от 1 до n. Если бы я кэшировал эти значения, конечно, вычисления могли бы быть гораздо быстрее. Если мы уже однажды рассчитали какое-то значение, то сохраним его для дальнейшего использования, например, в HashMap:
public class FactorialUtil
{
    static HashMap<Integer,BigInteger> cache = new HashMap<Integer,BigInteger>();

    public static BigInteger factorial(int n)
    {
        BigInteger ret;

        if (n == 0) return BigInteger.ONE;
        if (null != (ret = cache.get(n))) return ret;
        ret = BigInteger.valueOf(n).multiply(factorial(n-1));
        cache.put(n, ret);
        return ret;
    }
}


Достаточно просто, правда?
Каждый из этих методов имеет свои достоинства и недостатки, поэтому, учитывая, что эта библиотека, вероятно, ещё не раз пригодится нам в будущем, стоит использовать стандартный механизм, популярный в Java-библиотеках. Я говорю о системе подключаемых (pluggable) модулей, которая позволяет во время выполнения (at runtime) решать, какой именно алгоритм использовать: медленный, но потребляющий меньше памяти, либо быстрый, но потребляющий больше памяти. Для начала нам надо переделать наш класс в Singleton, потому что любые подключаемые штуковины требуют инициализированных классов и сиглтон, возвращающий реализацию по умолчанию.

Итак, мы создаём класс, работа которого заключается в поддержке синглтона для нашей фабрики (Factory class), и ссылки на алгоритм, реализующий метод. Этот класс предоставляет старый интерфейс, который был показан выше, а также позволяет использовать новый, улучшенный алгоритм:
public class FactorialUtil
{
    private static FactorialUtil singleton;
    private FactorialAlgorithm algorithm;

    /**
     * Default (internal) constructor constructs our default algorithm.
     */
    private FactorialUtil()
    {
        algorithm = new CachedFactorialImplementation();
    }

    /**
     * New initializer which allows selection of the algorithm mechanism
     * @param algorithm
     */
    public FactorialUtil(FactorialAlgorithm a)
    {
        algorithm = a;
    }

    /**
     * Default public interface for handling our factorial algorithm. Uses
     * the old standard established earlier for calling into our utility class.
     * @param n
     * @return
     */
    public static BigInteger factorial(int n)
    {
        if (singleton == null) {
            // Use default constructor which uses default algorithm
            singleton = new FactorialUtil();
        }
        return singleton.doFactorial(n);
    }

    /**
     * New mechanism which allows us to instantiate individual factorial
     * utilitiy classes and invoke customized factorial algorithms directory.
     * @param n
     * @return
     */
    private BigInteger doFactorial(int n)
    {
        // Defer to our algorithm
        return algorithm.factorial(n);
    }
}


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

Он зависит от интерфейса алгоритма:
public interface FactorialAlgorithm
{
    BigInteger factorial(int n);
}


А вот — реализация, использующая кэширование промежуточных результатов, которую мы упоминали раньше:
public class CachedFactorialImplementation implements FactorialAlgorithm
{
    static HashMap<Integer,BigInteger> cache = new HashMap<Integer,BigInteger>();

    @Override
    public BigInteger factorial(int n)
    {
        BigInteger ret;

        if (n == 0) return BigInteger.ONE;
        if (null != (ret = cache.get(n))) return ret;
        ret = BigInteger.valueOf(n).multiply(factorial(n-1));
        cache.put(n, ret);
        return ret;
    }
}


Посмотрите, как прекрасна эта структура! Я имею в виду, что мы легко можем добавить не-кэширующую не-рекурсивную реализацию:
public class LoopedFactorialImplementation implements FactorialAlgorithm
{
    @Override
    public BigInteger factorial(int n)
    {
        BigInteger ret = BigInteger.ONE;
        for (int i = 1; i <= n; ++i) ret = ret.multiply(BigInteger.valueOf(i));
        return ret;
    }
}


Недостаток этого дизайна, с точки зрения Java, должен быть очевиден: он не позволяет нам выбрать алгоритм во время выполнения (at runtime) — а ведь это и была изначально наша главная задумка. То есть очевидно, нам надо загрузить конфигурацию и выбрать алгоритм согласно ей. Например, мы можем прочитать некоторое системное свойство (System property), которое содержит имя класса, реализующего алгоритм. В идеале наш главный метод должен выглядеть примерно так:
    public static void main(String[] args)
    {
        System.getProperties().setProperty("com.chaosinmotion.factorialalgorithm", "cachedAlgorithm");
        System.out.println("5! = " + FactorialUtil.factorial(5));
    }


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

Итак, нам нужна фабрика, которая могла бы генерировать алгоритмы. Мы храним как массив созданных фабрик-синглтонов, так и массив соответствий имён классов и реализаций в classMapping. Таким образом, мы не создаём объект класса-алгоритма, пока он нам реально не понадобится (нечего вызывать лишние конструкторы и тратить ресурсы без пользы).
/**
 * Factory class manages the factorial algorithms in our system.
 * @author wwoody
 *
 */
public class FactorialAlgorithmFactory
{
    private static HashMap<String,FactorialAlgorithm> mapping = new HashMap<String,FactorialAlgorithm>();
    private static HashMap<String,Class<? extends FactorialAlgorithm>> classMapping = new HashMap<String,Class<? extends FactorialAlgorithm>>();
    private static FactorialAlgorithm defaultAlgorithm = new CachedFactorialImplementation();

    /** Static initializer registers some of my known classes */
    static {
        try {
            Class.forName("com.chaosinmotion.factorial.LoopedFactorialImplementation");
            Class.forName("com.chaosinmotion.factorial.CachedFactorialImplementation");
        }
        catch (ClassNotFoundException e) {
            // Should never happen.
        }
    }

    /** Get the default algorithm for computing factorials */
    public static FactorialAlgorithm getDefaultAlgorithm()
    {
        if (defaultAlgorithm == null) {
            // Warning: this will fail if for whatever reason CachedFactorialImplementation
            // is not in the class path.
            defaultAlgorithm = getAlgorithm("cachedAlgorithm");
        }
        return defaultAlgorithm;
    }

    /** Get the factorial algorithm specified by name */
    public static FactorialAlgorithm getAlgorithm(String name)
    {
        FactorialAlgorithm f = mapping.get(name);
        if (f == null) {
            // We haven't created an instance yet. Get it from the class mapping.
            Class<? extends FactorialAlgorithm> c = classMapping.get(name);
            if (c != null) {
                // Create a new instance of the factorial algorithm specified
                try {
                    f = c.newInstance();
                    mapping.put(name, f);
                    return f;
                }
                catch (Exception e) {
                    // Log the error
                    Logger.getLogger("com.chaosinmotion.factorial").
                    warning("Unable to instantiate algorithm " +
                            c.getCanonicalName() + ", named " + name);
                }
            }
            return getDefaultAlgorithm(); // return something.
        }
        else return f;
    }

    /** Register the class so we can construct a new instance if not already initialized */
    public static void registerAlgorithm(String name, Class<? extends FactorialAlgorithm> f)
    {
        classMapping.put(name, f);
    }
}


Перепишем класс FactorialUtil, так чтобы он использовал наши именованные алгоритмы:
public class FactorialUtil
{
    private static FactorialUtil singleton;
    private FactorialAlgorithm algorithm;

    /**
     * Default (internal) constructor constructs our default algorithm.
     */
    private FactorialUtil()
    {
        String name = System.getProperty("com.chaosinmotion.factorialalgorithm", "cachedAlgorithm");
        if (name == null) {
            algorithm = FactorialAlgorithmFactory.getDefaultAlgorithm();
        } else {
            algorithm = FactorialAlgorithmFactory.getAlgorithm(name);
        }
    }

    /**
     * New initializer which allows selection of the algorithm mechanism
     * @param algorithm
     */
    public FactorialUtil(FactorialAlgorithm a)
    {
        algorithm = a;
    }

    /**
     * Utility to create by name. Calls into FactorialAlgorithmFactory to
     * actually get the algorithm.
     * @param name
     */
    public FactorialUtil(String name)
    {
        algorithm = FactorialAlgorithmFactory.getAlgorithm(name);
    }

    /**
     * Default public interface for handling our factorial algorithm. Uses
     * the old standard established earlier for calling into our utility class.
     * @param n
     * @return
     */
    public static BigInteger factorial(int n)
    {
        if (singleton == null) {
            // Use default constructor which uses default algorithm
            singleton = new FactorialUtil();
        }
        return singleton.doFactorial(n);
    }

    /**
     * New mechanism which allows us to instantiate individual factorial
     * utilitiy classes and invoke customized factorial algorithms directory.
     * @param n
     * @return
     */
    private BigInteger doFactorial(int n)
    {
        // Defer to our algorithm
        return algorithm.factorial(n);
    }
}


А также нам понадобится добавить в классы CachedFactorialImplementation и LoopedFactorialImplementation блоки статической инициализации (static class initializers), которые зарегистрируют их в фабрике:
public class CachedFactorialImplementation implements FactorialAlgorithm
{
    static HashMap<Integer,BigInteger> cache = new HashMap<Integer,BigInteger>();

    static {
        FactorialAlgorithmFactory.registerAlgorithm("cachedAlgorithm", CachedFactorialImplementation.class);
    }

    @Override
    public BigInteger factorial(int n)
    {
        BigInteger ret;

        if (null != (ret = cache.get(n))) return ret;
        ret = BigInteger.valueOf(n).multiply(factorial(n-1));
        cache.put(n, ret);
        return ret;
    }
}


и

public class LoopedFactorialImplementation implements FactorialAlgorithm
{
    static {
        FactorialAlgorithmFactory.registerAlgorithm("loopedAlgorithm", LoopedFactorialImplementation.class);
    }
    @Override
    public BigInteger factorial(int n)
    {
        BigInteger ret = BigInteger.ONE;
        for (int i = 1; i <= n; ++i) ret = ret.multiply(BigInteger.valueOf(i));
        return ret;
    }
}


Наивысшая красота этой архитектуры заключается в том, что мы можем на лету в FactorialUtil подключить свою собственную реализацию факториала. Для этого надо всего лишь создать свой класс, реализующий интерфейс FactorialAlgorithm, и зарегистрировать его через FactorialAlgorithmFactory в блоке статической инициализации:
public class RecursiveFactorialImplementation implements FactorialAlgorithm
{
    static {
        FactorialAlgorithmFactory.registerAlgorithm("recursiveAlgorithm", RecursiveFactorialImplementation.class);
    }

    @Override
    public BigInteger factorial(int n)
    {
        if (n == 0) return BigInteger.ONE;
        return BigInteger.valueOf(n).multiply(factorial(n-1));
    }
}


И наконец, в главном методе мы убеждаемся, что наш класс загружен, и устанавливаем системное свойство.
    public static void main(String[] args)
    {
        try {
            Class.forName("com.chaosinmotion.factorial.RecursiveFactorialImplementation");
        }
        catch (ClassNotFoundException e) {
            // if this fails, no matter; we'll still use the default implementation.
        }
        System.getProperties().setProperty("com.chaosinmotion.factorialalgorithm", "recursiveAlgorithm");
        System.out.println("5! = " + FactorialUtil.factorial(5));
    }


Нет проблем! Более того, эта архитектура позволяет подключать и более изощрённые решения, такие как эти.

separator.png


Я уверен, что многие Java-программисты, дойдя до этого места, кивают головами и восхищаются изящностью этой архитектуры. Найдутся и такие, кто уже жмёт кнопку «Оставить комментарий» и пишет: «Чёрт возьми, тебе лучше было бы установить системные свойства так-то и так-то». Например, я мог бы поместить инициализатор для разных классов в файл с свойствами (*.properties) или в XML-файл. Или может, лучше было бы, чтобы значением системного свойства было полное имя класса.

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

Но погодите, вот наконец моя главная мысль.

Всё это хрень.

Каждая-прекаждая строчка.

Несомненно, в некоторых обстоятельствах подключаемая архитектура полезна и даже необходима. Но это бывает достаточно редко — настолько редко, что это даже несмешно. В 99% случаев, когда я видел подобный код, он был целиком и полностью бесполезным. Он скрывает истинное назначение кода, заменяя двух-трёхстрочный вспомогательный метод десятками и даже сотнями строчек самодовольного напыщенного буллшита на Java. Возможно, он помогает вам чувствовать себя хорошо, но он создаёт уродливый беспорядок, который будущим разработчикам придётся подчистить, или скорее всего избегать как чумы.

А самое интересное, вы заметили кое-что? В течение всей этой дискусии, вы ничего не заметили?

Мы ни разу не позаботились об отрицательных числах.

separator.png


Умный Java-разработчик знает, когда остановиться. Жизнь слишком коротка, чтобы строить замки в облаках. Он знает, что простого решения с циклом более чем достаточно, и конечно, он позаботится об отрицательных числах. (Ну вы в курсе, да? Наше рекурсивное решение впадёт в бесконечный цикл, если дать на входе отрицательное число.)

По-настоящему умный Java-разработчик может погрузиться в проблему чуть глубже и узнать, что факториал — это частный случай Гамма-функции. Возможно, правильное решение — это вовсе и никакой из приведённый выше кусков кода, а вообще аппроксимация Стирлинга для гамма-функции:
static double Gamma(double z)
    {
        double tmp1 = Math.sqrt(2*Math.PI/z);
        double tmp2 = z + 1.0/(12 * z - 1.0/(10*z));
        tmp2 = Math.pow(z/Math.E, z); // ooops; thanks hj
        tmp2 = Math.pow(tmp2/Math.E, z);
        return tmp1 * tmp2;
    }


Но это уже зависит от проблемной области — о которой мы вообще-то даже и не думали, создавая все эти наши фабрики.

separator.png


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

Но будущее никогда не становится проще, верно?

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

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

separator.png


Поэтому пожалуйста, сделайте всем нам одолжение: если вы испытываете желание добавить сложности просто потому, что «когда-нибудь нам это пригодится» или «система пока недостаточно гибкая» или «в нашем коде должно быть повторное использование (reusability)» или (не дай бог!) потому что «это круто» — просто уйдите домой пораньше. Посмотрите мультики. Или возьмите в прокате "Начало".

Перестаньте создавать нам дополнительную работу без хорошей на то причины.



Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 36

    +14
    Предыдущий перевод этой же статьи собрал 116 комментариев и +98 рейтинга:

    habrahabr.ru/blogs/java/112969/

      +11
      Ну зато тут есть тег «переинженеринг», я раньше не слышал, но весьма остроумно. Добавил к своему переводу :)
        +5
        Молодец, не расстраивайтесь.
        Я сам из другого лагеря ( C# ), эту статью читал в первый раз.
        Спасибо! Статья и перевод понравились.

          +2
          Вы, наверное, не так поняли: я автор того, первого, перевода, и оценил один из тэгов этого.
            +1
            Опа, Вы правы, я не понял.
          –1
          Vot, kstati, ewe odna perevodnaja statja pro etot termin:
          habrahabr.ru/blogs/gtd/99889/
          +5
          Опа!
          В том переводе пропущена большая и самая важная часть!
            0
            Значит остальное можно убрать и поставить ссылку на тот топик.
          –2
          Возможно я тормоз, но странный у вас синглтон получился. В смысле ничто не мешает несколько экземпляров класса FactorialUtil получить, как с одинаковыми, так и с разными алгоритмами, какой же это синглтон?
            +2
            William Woody вряд ли зайдет на Хабр ответить на комментарии, так что и обращаться к нему бессмысленно.
              +3
              Кто-нибудь, вышлите Вильяму Вуди инвайт
                0
                А я думал переводчик переводит комментарии и форвардит их в блог с оригинальной статьей (чешу репу, предварительно сделав глупое выражение лица).
              +3
              Объявляется неделя факториала!
                +5
                прирост +10 существ!
                  +1
                  прирост +10 алгоритмов и решений :)

                  Но концовочка в этом переводе действительно ценная.
                  • UFO just landed and posted this here
                  +6
                  В главе 24 книги «Совершенный код» С. Макконнелл говорит: «Мнение экспертов по этому поводу [программа содержит код, который может когда-нибудь понадобиться] однозначно: если вы хотите наилучшим образом подготовиться к будущим требованиям, не пишите гипотетически нужный код, а уделите повышенное внимание ясности и понятности кода, который нужен прямо сейчас, чтобы будущие программисты знали, что он делает, чего не делает, и могли быстро и правильно изменить его».
                    0
                    вот и основная мысль рефакторинга как раз в этом — пишите код который нужен именно сейчас, не думайте о будущем. НО пишите код так чтобы его можно было с легкостью изменить в будущем под нужные требования. Если брать этот пример с факториалом, то было бы достаточно пары строк метода CalcFactorial например, а если его в будущем нужно будет расширить то например можно будет сделать поднятие/опускание метода, или выделение класса ну и тд.
                      0
                      Собственно, это и есть глава «Рефакторинг» :)
                        +1
                        у макконелла рефакторинг сжатенько написан. Лучше прочитать фаулера. Там более подробно
                    0
                    System.getProperty(«com.chaosinmotion.factorialalgorithm», «cachedAlgorithm»);
                    Этот вызов не может вернуть null насколько я знаю.

                    Вообще топик- расширенное объяснение принципа KISS )
                      +4
                      На самом деле, всё зависит от контекста, в которой решается данная задача. Автор _почти_ прав насчёт избыточности абстракций для вычисления факториала. Но есть, как минимум, одна ситуация, в которой был бы оправдан изложенный в статье подход — если всё это часть какой-либо математической библиотеки, где вполне имеет смысл использование различных реализаций. Хотя и в этом случае, я, пожалуй, не стал бы злоупотреблять фабриками алгоритмов вычисления факториалов.
                      И да, не стоит бросаться в крайности. Избыточность слоёв абстракции, ровно как и полное их отсутствие — зло. Путь добра, как всегда, посередине
                      –1
                      По-моему, читаемость самого первого варианта кода наиболее высока. Человек, которому достанется поддерживать данный код, сразу поймет, что перед ним факториал. В случае же последнего варианта он потратит много времени на то, что бы понять что и как работает.
                        0
                        При наличии комментариев — думаю проблем быть не должно. Вопрос в другом — нужно ли когда-то в принципе подменять имплементацию мат функции в рантайме? Решение о пределах, времени выполнения зависит от алгоритма, где конкретная функция используется
                        –2
                        Вот только Ява то тут причем? :)
                        Этим страдают представители очень многих языков. Умение чувствовать «золотую середину» — очень ценный скилл разработчика.
                          0
                          Этим страдают не языки, а некоторые разработчики с Шаблонизацией и Архитектуризацией головного мозга.
                            +1
                            Угу… недавно на Хабре проскакивало: «пахлава-код» :)
                              +2
                              Мне по этому поводу больше понравилась фраза «ООПухоль мозга», тоже недавно встретил на Хабре :)
                            +1
                            Имхо, именно в Java это сильно проявляется. Причина — существующие монструозные решения, которые пытаются создать абстракции на все и вся. Новые разработчики смотрят на все это и думают что подобный подход хорош…

                            Недавно была статья о логировании, там писалось об обертке поверх обертки.
                            Чего только стоит тот же самый Spring, в котором, отчасти и из-за плохой документации, хрен разберешься. Я как-то потратил примерно неделю (каждый вечер) в попытках разобраться как работает Spring Security и так и не смог. Я понял что мне быстрее написать свое простенькое решение, чем настраивать этого монстра.
                            А уж чего стоят библиотеки Java-XML маппинга — это вообще жесть.
                            И т.п.
                            +2
                            Это не то чтобы проблема Java, это проблема энтерпрайза как такового. Во Flex любят городить такие же лишние абстракции. При этом самое страшное — новички считают, что это единственный абсолютно правильный метод программирования, и чем больше уровней абстракции, тем код правильнее.

                            Я, например, в последнее время часто бью себя по рукам, чтобы не нагородить такого барахла где не нужно.
                              –1
                              А еще можно использовать формулу Стирлинга:
                              ru.wikipedia.org/wiki/%D0%A4%D0%BE%D1%80%D0%BC%D1%83%D0%BB%D0%B0_%D0%A1%D1%82%D0%B8%D1%80%D0%BB%D0%B8%D0%BD%D0%B3%D0%B0
                              Для больших значений получается хорошая точность и гораздо быстрее.
                                0
                                1) Я не понимаю, почему в функции аппроксимации Java считается tmp2 при присваивании, когда потом он не используется.

                                Вот правильный код (осторожно! Си!):
                                static double Gamma(double z)
                                {
                                double tmp1 = sqrt(2*M_PI/z);
                                double tmp2 = z + 1.0/(12 * z — 1.0/(10*z));
                                tmp2 = pow(tmp2/M_E, z);
                                return tmp1 * tmp2;
                                }

                                2) Если BigInteger в Java работает подобно GMP, то имеет смысл умножать числа блоками по несколько тысяч, так не нужно постоянно растить размер буффера и на согласованных по размеру блоках быстрее работает FFT.

                                3) Лично я всё равно не могу придумать места, где нужно считать факториал хотя-бы тысячи.
                                  0
                                  Пишите код по сановским соглашениям, if'ы без скобок неудобно читать же.
                                    +2
                                    Напомнило один анекдот,
                                    где показывалось, как пишут код программисты в зависимости от уровня, от джуниора до босса.
                                    сначала идет такое усложнение, до тимлида, а потом упрощение до одной строки у босса и у хакера.
                                      0
                                      Я понимаю, что статья о том, как «Как не надо писать %s в Java», но прежде чем писать реализацию какой-то сложной функции и делать к ней всякие ускорения в виде кэширующих классов, нужно больше узнать про эту функцию, то есть абстрагироваться от темы «Как не надо писать факториал в Java» до «Как не надо писать факториал». Дело в том, что существуют алгоритмы быстрого вычисления факториалов, они легко гуглятся (но, некоторые из тех что я нашёл вычисляют факториал не точно).

                                      Only users with full accounts can post comments. Log in, please.