Unity 6.3
0 онлайн 55 гостей 3 в системе
Вход
Руководство по стилю C# для чистого и масштабируемого кода Глава 7 из 13 Оригинал, стр. 43

Методы

Русский

Методы

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

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

Хорошее имя метода отражает его назначение. Например, GetDistanceToTarget ясно описывает цель метода. Если в проекте используются разные единицы измерения, можно выбрать еще более точное имя GetDistanceToTargetInMeters, показывающее, что расстояние возвращается в метрах.

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

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

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

— Вместо флага создайте отдельный метод. Не заставляйте один метод работать в двух режимах в зависимости от флага; создайте два метода с разными именами. Например, не используйте GetAngle, который по флагу возвращает градусы или радианы. Создайте GetAngleInDegrees и GetAngleInRadians. . Булев флаг в списке аргументов выглядит безобидно, но может запутать реализацию или нарушить принцип единственной ответственности.

Методы или функции? В Unity и C# принято говорить о методах, а не о функциях, поскольку метод — это функция, определенная в контексте класса или объекта. C# является объектно-ориентированным языком: его основа — классы и объекты, а методы описывают действия объектов. Функция — более общее название блока кода, который выполняет определенную задачу. В процедурных языках функции могут существовать независимо, но в объектно-ориентированных языках, таких как C#, они инкапсулированы в классах и называются методами.

Методы расширения Методы расширения позволяют добавлять функциональность в классы, в том числе закрытые для наследования, и дают аккуратный способ расширять API UnityEngine. Чтобы создать метод расширения, объявите статический метод и поставьте ключевое слово this перед первым параметром — типом, который требуется расширить. Предположим, нужно создать метод ResetTransformation, который удаляет масштабирование, поворот и смещение объекта GameObject.

Можно создать статический метод, указав первым параметром Transform и поставив перед ним ключевое слово this:

C#
// ПРИМЕР: определение метода расширения

public static class TransformExtensions {
    public static void ResetTransformation(this Transform transform) {
        transform.position = Vector3.zero;
        transform.localRotation = Quaternion.identity;
        transform.localScale = Vector3.one;
    }
}

Затем, когда понадобится применить этот метод, вызовите ResetTransformation. Класс ResetOnStart вызывает его для текущего Transform в методе Start.

C#
// ПРИМЕР: вызов метода расширения

public class ResetOnStart : MonoBehaviour {
    void Start() { transform.ResetTransformation(); }
}

Чтобы упорядочить код, определяйте методы расширения в статических классах. Например, методы, расширяющие Transform, можно поместить в класс TransformExtensions, методы для Vector3 — в Vector3Extensions и так далее. Методы расширения позволяют создавать множество полезных утилит без дополнительных компонентов MonoBehaviour. Подробнее см. материал Unity Learn «Методы расширения»: он поможет пополнить арсенал приемов разработчика игр.

Принцип DRY: не повторяйтесь В книге «Программист-прагматик» Энди Хант и Дейв Томас сформулировали принцип DRY — «не повторяйтесь». Этот известный принцип разработки программного обеспечения рекомендует избегать дублирования и повторения логики. Так проще исправлять ошибки и снижать стоимость сопровождения. Если вы следуете принципу единственной ответственности, при изменении класса или метода не придется править несвязанный код. Устранив логическую ошибку в коде, соответствующем принципу DRY, вы исправите ее сразу во всех местах.

Противоположность DRY — WET («нам нравится печатать» или «пишите все дважды»). Код считается WET, если в нем есть ненужные повторения. Представьте две системы ParticleSystem (explosionA и explosionB) и два объекта AudioClip (soundA и soundB). Каждая система ParticleSystem должна воспроизводиться вместе со своим звуком. Для этого можно написать простые методы, подобные следующим.

C#
// ПРИМЕР: ПИШЕМ ВСЕ ДВАЖДЫ

private void PlayExplosionA(Vector3 hitPosition) {
    explosionA.transform.position = hitPosition;
    explosionA.Stop();
    explosionA.Play();
    AudioSource.PlayClipAtPoint(soundA, hitPosition);
}
private void PlayExplosionB(Vector3 hitPosition) {
    explosionB.transform.position = hitPosition;
    explosionB.Stop();
    explosionB.Play();
    AudioSource.PlayClipAtPoint(soundB, hitPosition);
}

Каждый метод принимает позицию типа Vector3, перемещает туда ParticleSystem и запускает эффект. Сначала система частиц останавливается на случай, если она уже воспроизводится, а затем симуляция запускается заново. После этого статический метод AudioSource.PlayClipAtPoint создает звуковой эффект в той же точке. Один метод фактически является копией другого с небольшими заменами. Такой код работает, но для каждого нового взрыва придется создавать еще один метод с той же логикой. Вместо этого выполните рефакторинг и сведите логику к одному методу PlayFXWithSound:

C#
// ПРИМЕР: версия после рефакторинга по принципу DRY

private void PlayFXWithSound(ParticleSystem particle, AudioClip clip, Vector3 hitPosition) {
    particle.transform.position = hitPosition;
    particle.Stop();
    particle.Play();

AudioSource.PlayClipAtPoint(clip, hitPosition); } Добавляя новые объекты ParticleSystem и AudioClip, вы сможете по-прежнему воспроизводить их совместно тем же методом. Обратите внимание: дублировать код можно и без нарушения принципа DRY. Главное — не дублировать логику. Здесь основная функциональность вынесена в метод PlayFXWithSound. Если логику потребуется изменить, достаточно исправить один метод, а не оба — PlayExplosionA и PlayExplosionB.

English

Methods

You know you are working on clean code when each routine you read turns out to be pretty much what you expected. – Ward Cunningham, inventor of Wiki and cofounder of eXtreme Programming

Like classes, methods should be small with a single responsibility. Each method should describe one action or answer one question. It shouldn’t do both. A good name for a method reflects what it does. For example, GetDistanceToTarget is a name that clarifies its intended purpose. Some will argue that being even more explicit with GetDistanceToTargetInMeters to make it clear that the distance is being returned in meters is also preferable, if your project has multiple units of measurement. Try the following suggestions when you create methods for your custom classes: —

Use fewer arguments: Arguments can increase the complexity of your method. Reduce their number to make your methods easier to read and test.

Avoid excessive overloading: You can generate an endless permutation of method overloads. Select the few that reflect how you will call the method and implement those.

If you do overload a method, prevent confusion by making sure each method signature has a distinct number of arguments. Avoid side effects: A method only needs to do what its name advertises. Avoid modifying anything outside of its scope. Pass in arguments by value instead of by reference when possible. If sending back results via the out or ref keyword, make sure that’s the one thing you intend the method to accomplish.

Though side effects are useful for certain tasks, they can lead to unintended consequences. Write a method without side effects to cut down on unexpected behavior. Instead of passing in a flag, make another method: Don’t set up your method to work in two different modes based on a flag. Make two methods with distinct names. For example, don’t make a GetAngle method that returns degrees or radians based on a flag setting. Instead make methods for GetAngleInDegrees and GetAngleInRadians.

While the Boolean flag as an argument seems innocuous, it can lead to tangled implementation or broken single-responsibility.

Methods vs functions? In Unity and C#, we refer to methods rather than functions because methods are functions that are defined within the context of a class or object. Since C# is an objectoriented programming (OOP) language, everything revolves around classes and objects, and methods are the actions that those objects can perform. A function, on the other hand, is a more generic term for a block of code that performs a specific task. In procedural programming languages, functions can exist independently, but in object-oriented languages like C# they are encapsulated within classes and are thus called methods.

Extension methods Extension methods offer a way to add additional functionality to classes that might otherwise be sealed and can be a clean way to extend the UnityEngine API. To create an extension method, make a static method and use the this keyword before the first argument, which will be the type you want to extend. For example, suppose you want to make a method called ResetTransformation to remove any scaling, rotation, or translation from a GameObject.

You can create a static method passing in a Transform for the first argument with the this keyword:

C#
// EXAMPLE: Define an extension method

public static class TransformExtensions {
    public static void ResetTransformation(this Transform transform) {
        transform.position = Vector3.zero;
        transform.localRotation = Quaternion.identity;
        transform.localScale = Vector3.one;
    }
}
Then, when you want to use it, invoke the ResetTransformation method. The ResetOnStart class calls it on the current Transform during Start. // EXAMPLE: Calling the extension method

public class ResetOnStart : MonoBehaviour {
    void Start() { transform.ResetTransformation(); }
}
For organization purposes, define your extension methods in a static class. For example, you create a class called TransformExtensions for methods that extend Transforms, Vector3Extensions for extending Vector3s, and so on. Extension methods can build many useful utilities without the need to create more Monobehaviours. See Unity Learn: Extension Methods to add them to your gamedev bag of tricks.

The DRY principle: Don’t repeat yourself In The Pragmatic Programmer, Andy Hunt and Dave Thomas formulated the DRY principle, or, “don’t repeat yourself.” This oft-spoken mantra in software engineering advises programmers to avoid duplicate or repetitious logic. In doing so, you can ease bug fixing and maintenance costs. If you follow the singleresponsibility principle, you shouldn’t need to change an unrelated piece of code whenever you modify a class or a method. Quashing a logical bug in a DRY program stops it everywhere.

The opposite of DRY is WET (“we enjoy typing” or “write everything twice”). Programming is WET when there are unnecessary repetitions in the code. Imagine there are two ParticleSystems (explosionA and explosionB) and two AudioClips (soundA and soundB). Each ParticleSystem needs to play with its respective sound, which you can achieve with simple methods like this.

C#
// EXAMPLE: WRITE EVERYTHING TWICE

private void PlayExplosionA(Vector3 hitPosition) {
    explosionA.transform.position = hitPosition;
    explosionA.Stop();
    explosionA.Play();
    AudioSource.PlayClipAtPoint(soundA, hitPosition);
}
private void PlayExplosionB(Vector3 hitPosition) {
    explosionB.transform.position = hitPosition;
    explosionB.Stop();
    explosionB.Play();
    AudioSource.PlayClipAtPoint(soundB, hitPosition);
}
Here each method takes a Vector3 position to move the ParticleSystem into place for playback. First, stop the particles (in case they are already playing) and play the simulation. The AudioSource’s static PlayClipAtPoint method then creates a sound effect at the same location. One method is a cut-and-paste version of the other with a little text replacement. Though this works, you need to make a new method – with duplicate logic – every time you want to create an explosion. Instead, refactor it into one PlayFXWithSound method like this: // EXAMPLE: Refactored DRY version

private void PlayFXWithSound(ParticleSystem particle, AudioClip clip, Vector3 hitPosition) {
    particle.transform.position = hitPosition;
    particle.Stop();
    particle.Play();

AudioSource.PlayClipAtPoint(clip, hitPosition); } Add more ParticleSystems and AudioClips and you can continue using this same method to play them in concert. Note that it’s possible to duplicate code without violating the DRY principle. It’s more important that you don’t duplicate logic. Here, we’ve extracted the core functionality into the PlayFXWithSound method. If you need to adjust the logic, you only need to change it in one method rather than in both PlayExplosionA and PlayExplosionB.