Unity 2019 游戏开发模式实用指南(三)

原文:zh.annas-archive.org/md5/0d8069504d523342a2a766d28f0c7801

译者:飞龙

协议:CC BY-NC-SA 4.0

第十九章:空间划分

在本章中,我们将回顾空间划分模式;空间划分的概念在计算机图形学中很普遍,用于以最佳方式组织虚拟空间中的对象。这种方法也适用于管理 Unity 场景中放置的 GameObject。通过实现空间划分模式的核心原则,我们可以将充满二维或三维对象的大型环境划分为两部分,同时仍然能够保持一定程度的性能一致性。正如您在本章中将会看到的,这种模式是使大型 AAA 开放世界游戏制作成为可能的核心成分之一。

本章将涵盖以下主题:

  • 我们将回顾空间划分模式背后的基本原理

  • 我们将实现一个迷你游戏,其中捕食者在场景中猎捕猎物

技术要求

本章是实践性的,您需要对 Unity 和 C#有基本的了解。

我们将使用以下特定的 Unity 引擎和 C#语言概念:

  • LINQ

如果您不熟悉这个概念,请在继续之前进行复习。

LINQ 是一种非常强大的查询语言,与 SQL 有些相似;当您想简单地遍历数据结构时,它可以节省时间。

本章的代码文件可以在 GitHub 上找到:

github.com/PacktPublishing/Hands-On-Game-Development-Patterns-with-Unity-2018

查看以下视频,以查看代码的实际运行情况:

bit.ly/2FAyWCf

空间划分模式的概述

游戏程序员经常面临的问题是如何快速定位场景中与参考点最近的特定实体,例如玩家角色。在 Unity 中,有许多解决这个问题的方法,如下所示:

  • 实现一个射线投射系统,该系统将扫描玩家角色周围的区域,并报告特定实体的位置。

  • 使用 Unity 的 API 功能,例如GameObject.Find()函数,在场景中定位特定实体,然后比较它们的坐标与玩家角色的坐标。

第一个选项是有效的,但如果我们的三维环境复杂,可能很难定位我们正在寻找的所有实体,因为它们可能被其他对象遮挡,无法被射线相交。第二个选项在性能方面可能不是最佳选择,因为我们可能需要遍历包含场景中每个实体的列表,直到找到特定类型的每个实例。

我们可以通过使用空间分区模式来解决这种类型的技术挑战;它是为此目的而设计的。我们需要首先解决的问题是这个模式的名字。术语空间分区可能会误导:我们不是在组织或修改我们正在分区的虚拟空间。我们做的是相反的;我们在方程中移除空间。

我们通过将场景中的三维物体放入一个平面数据结构中来实现这一点,该数据结构在内存中有效地表示这些物体之间的距离,而无需对精确坐标进行计算。这种方法允许我们快速直接地计算找到最接近或最远离参考点的实体。

换句话说,我们正在将虚拟空间细分成一个更容易分析的结构。一个易于在内存中表示的通用结构(通常用于将空间划分为单个容器)是一个固定网格。在以下图中,您可以看到这个概念的可视表示。网格包含正方形,我们将它们称为单元格。这些单元格包含单位。这些单位可以是任何东西——一个特定的敌人角色类型或散布在广阔地图上的隐藏宝藏箱:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/hsn-gmdev-ptn-unt-2019/img/159db14d-2aec-488b-8a74-63cad602f3eb.png

现在,让我们想象这个网格叠加在一个大型开放世界地图的 RPG 视频游戏上。每个单元格(正方形)代表一个虚拟的 2x2 平方公里区域。我们知道我们的玩家角色在地图上的一个特定单元格(正方形)中出生,但我们想给他提供快速前往一个充满他可以战斗的 2 级怪物的区域的选择。通过使用空间分区,我们可以轻松地计算内存中特定类型的最近实体,而无需扫描整个三维环境。

计算结果可以向我们建议一个包含 2 级敌人最大群体的附近单元格(正方形)。有了这个信息,我们可以将我们的玩家角色移动到建议单元格(正方形)内的随机位置,以便他可以掠夺该区域。正如您将在下一节中看到的那样,空间分区简化了管理位于复杂二维和三维空间中的实体的过程。

优点和缺点

这种模式的缺点相当有限(在大多数情况下并不存在),因为它非常容易使用。

优点如下:

  • 可重用性:我们可以使用空间分区模式来优化我们管理由二维或三维空间中的实体组成的事物的方式(例如,用户界面)。

  • 简化:空间分区使得实现计算对象之间空间关系的代码变得更加容易。这对数学能力不强的人来说非常有用。

缺点是:

  • 不太动态:如果你试图管理在广阔区域内不断移动的实体,空间划分可能会失去其所有的优化优势。因此,如果你有一个场景中充满了以全速弹跳的物体,你需要不断地更新包含实体及其网格位置的集合的数据结构。在这种情况下,这个过程可能会消耗大量资源,不值得付出努力。

游戏程序员应该掌握的最重要技能是数学。了解设计模式对于进入行业是必要的,但它并不像对高级数学的深入理解那样重要。

用例示例

假设我们需要快速原型化一个简单的迷你游戏,该游戏模拟一个非玩家捕食者角色在地图上狩猎猎物。在环境中生成实体(猎物和捕食者)的过程并不复杂;实际上,它相当简单。然而,我们如何知道我们的捕食者是否接近潜在的猎物并将其移动到那里呢?

考虑以下可能的解决方案:

  • 我们可以查询场景中的每个物体,并将它们的坐标与捕食者的坐标进行比较。

  • 我们可以实现一个射线投射系统,扫描捕食者附近的每个物体,以发现潜在的猎物。

这些解决方案可能有效,但它们在短时间内实施可能会很繁琐。然而,使用空间划分模式,我们可以通过确保场景中的所有实体都包含在一个按相对位置组织猎物和捕食者的数据结构中来避免这个过程。正如你将在我们的代码示例中看到的那样,编写这个实现非常快且有用,尤其是在你匆忙并想在代码中草拟一些基本的 AI 导航行为时。

代码示例

以下代码示例可能看起来非常基础,但它可以很容易地扩展以实现更复杂的使用案例。从某种意义上说,它是一个我们将能够在此基础上构建的基础:

  1. 让我们从实现我们模式的核心元素Grid开始:
using System;
using System.Linq;
using UnityEngine;

public class Grid: MonoBehaviour
{
    private int m_SquareSize;
    private int m_NumberOfSquares;

    public Grid(int squareSize, int numberOfSquares)
    {
        // The size can represent anything (meters, km)
        m_SquareSize = squareSize;

        // Squares permits to subdivide the grid granulary
        m_NumberOfSquares = numberOfSquares;
    }

    public void AddToRandomnPosition(IUnit unit)
    {
        unit.AddToGrid(UnityEngine.Random.Range(0, m_NumberOfSquares));
    }

    public int FindClosest(IUnit referenceUnit, IUnit[] list)
    {
        if (list != null)
        {
            // Using LINQ queries
            var points = list.Select(a => a.GetGridPosition()).ToList();
            var nearest = points.OrderBy(x => Math.Abs(x - referenceUnit.GetGridPosition())).First();
            return nearest;
        }
        else
        {
            throw new ArgumentException("Parameters cannot be null", "list");
        }
    }
}

你应该注意的第一件事是AddToRandomnPosition()函数,在其中我们通过Random.Range()调用将单位添加到网格中的方块。我们这样做有两个原因。我们想快速测试我们的Grid实现,所以我们模拟了实体在随机位置的环境中分散。我们还想展示我们如何结合使用空间划分和生成系统来管理特定优化网格空间内的实体生成。换句话说,我们可以在初始化将占据它的东西之前,在内存中对场景的虚拟空间进行划分。

另一个需要分析的功能是FindClosest();请注意,我们使用了两个 LINQ 查询。第一个查询从一个单位列表中提取网格位置列表。第二个查询是查询这个列表,以找到相对于参考单位最近的单元格。对于那些从未使用过 LINQ 的人来说,它是一种内置的 C#查询语言,允许使用一行代码在集合中查找和提取元素。当你需要原型设计和快速编写使用数据结构和集合的实现时,这是一个非常出色的工具。

  1. 现在,我们需要一种方法让我们的单位将自己注册到Grid的特定单元格中。让我们首先实现一个接口来管理我们的单位类型:
public interface IUnit
{
    // The Unit can add itself to the grid
    void AddToGrid(int cell);

    // The Unit can return is current grid position
    int GetGridPosition();
}

这是一个相当直接的接口;GetGridPosition()函数返回Unit的网格位置。可能出现的疑问是,为什么我们不实现一个返回Unit在场景中实际位置的函数?这是因为,在 Unity 中,如果一个 GameObject 附加了Transform组件,我们可以直接要求这个组件返回它在三维场景中的位置。换句话说,我们正在使用 Unity 的 API 为我们做繁重的工作。

  1. 我们将为我们的代码示例实现两种类型的单位;让我们从Prey开始:
using UnityEngine;

public class Prey : MonoBehaviour, IUnit
{
    private int m_Square;

    public void AddToGrid(int square)
    {
        m_Square = square;
    }

    public int GetGridPosition()
    {
        return m_Square;
    }
}
  1. 接下来是我们的Predator类;他猎捕我们的Prey
using UnityEngine;

public class Predator : MonoBehaviour, IUnit
{
    private int m_Square;

    public void AddToGrid(int square)
    {
        m_Square = square;
    }

    public int GetGridPosition()
    {
        return m_Square;
    }
}

我们可以看到,我们的PredatorPrey都有两个主要职责,将它们的位置链接到网格中的特定单元格,并在需要时返回该单元格编号。

  1. 最后,我们的Client类,我们使用它来在Grid上生成Prey并释放Predator,如下所示:
using UnityEngine;

namespace Pattern.SpatialPartition
{
    public class Client : MonoBehaviour
    {
        private Grid m_Grid;
        private IUnit[] m_Preys;

        void Start()
        {
            m_Grid = new Grid(4, 16);
            Debug.Log("Grid generated");
        }

        void Update()
        {
            if (Input.GetKeyDown(KeyCode.P))
            {
                IUnit prey;
                int numberOfPrey = 5;
                m_Preys = new IUnit[numberOfPrey];

                for (int i = 0; i < numberOfPrey; i++)
                {
                    prey = new Prey();
                    m_Grid.AddToRandomnPosition(prey);
                    m_Preys[i] = prey;

                    Debug.Log("A prey was spawned @ square: " + m_Preys[i].GetGridPosition());
                }
            }

            if (Input.GetKeyDown(KeyCode.H))
            {
                IUnit predator;
                predator = new Predator();
                m_Grid.AddToRandomnPosition(predator);
                Debug.Log("A predator was spawned @ square: " + predator.GetGridPosition());

                int closest = m_Grid.FindClosest(predator, m_Preys);
                Debug.Log("The closest prey is @ square: " + closest);
            }
        }

        void OnGUI()
        {
            GUI.color = Color.black;
            GUI.Label(new Rect(10, 10, 500, 20), "Press P to spawn prey on the grid.");
            GUI.Label(new Rect(10, 30, 500, 20), "Press H to hunt some prey.");
            GUI.Label(new Rect(10, 50, 500, 20), "Open Debug Console to view the output.");
        }
    }
}

就这些了;请注意,我们从未需要处理对象的实际三维坐标来找到它们的相对位置。通过将空间划分为网格并将对象包含在其中,我们避免了大量的不必要的计算。我们通过分块来降低复杂性。

当然,在我们的代码示例中,我们选择了简单的方法,在将Units添加到Grid中的特定方格之前,避免了计算它们的相对位置,但如果需要的话,这可以很容易地添加。最重要的启示是,如果我们可以在一个可以轻松搜索和操作的数据结构中分区和管理它们,我们就应该始终避免在三维空间中的实体上进行复杂的计算。

摘要

在本章中,我们采取了一种简单的方法来学习一个提供复杂问题解决方案的模式,即如何在空间中优化组织对象。我们现在有一个可以用来构建开放世界游戏和快速原型化以网格为中心的游戏(例如,解谜游戏)的工具。

在本书的最后一章,我们将回顾一个与我们刚刚探讨的内容完全相反的主题:反模式,它是设计模式的对立面。

练习

在我们的代码示例中,我们实现了一个关于空间划分模式的直接应用案例。然而,我们仅限于二维空间;作为一个实际练习,我建议在此基础上扩展这个基本示例,并在三维空间中组织对象。作为灵感,我建议观察魔方的结构设计。注意,它由一系列小立方体组成;每个小立方体都可以被视为一个组中的单元格。

进一步阅读

第八部分:Unity 中的反模式

在本节中,我们将通过揭示它们的邪恶孪生兄弟——反模式,来探索设计模式的阴暗面。这些负面模式可能存在于你组织的每个层面,并以非常微妙的方式对你的代码库造成退化。

以下章节包含在本节中:

  • 第二十章,反模式

第二十章:反模式

在整本书中,我们通过实施各种类型的模式来回顾了软件架构的最佳实践。但你可能会问自己,如果这些模式如此有益,为什么不是每个人都使用它们?或者为什么我们仍然经常看到充斥着错误的游戏问世?

如果现在的程序员可以轻松地获取大量关于软件开发最佳实践的信息,那么合理地假设,我们不应该有理由在合理的时间内交付稳定的视频游戏和应用。但在本章中,我们将探讨为什么在软件开发行业中,即使是非常有能力和才华的团队最终也会生产出杂乱的代码,并且无法交付一个稳定的产物。

在前几章中,我们探讨了旨在有益并带来积极结果的模式。但现在,我们将研究它们的邪恶双胞胎,即反模式。这些破坏性的模式很微妙;它们并不总是潜伏在你的代码中,而是通过在每个组织层面引起恐惧、不确定性和怀疑来伤害你。这就是为什么它们如此难以识别,正如我们将在下一节中看到的那样。

本章将涵盖以下主题:

  • 我们将回顾一系列常见的反模式

反模式

目前,软件开发的每个领域都有超过一百种反模式被专家们记录下来。我们无法在本章中全部回顾,但我已经列出了一份简短的清单,其中包含我发现的一些与设计模式误用相关的内容,无论是直接还是间接的。但我还列出了我在职业生涯中亲身经历过的那些。

与已建立的设计模式相比,关于反模式的学术研究并没有得到充分的记录,因此在特定反模式的命名上存在很多差异。因此,以下材料中的很多内容都是我对普遍存在的反模式的解释,而不是官方定义。那么,现在让我们深入主题,回顾一些我亲身经历并推荐避免的最相关的反模式。

假装精通

“如果人们知道我为了达到精通付出了多少努力,那么这一切就不会显得那么神奇了。”

  • 米开朗基罗

这是什么? 程序员可以轻松访问大量信息、工具和库,使他们能够轻松地开发他们想要的任何东西。因此,这些优势使许多初级开发者相信他们是他们技艺的大师,当他们只是在复制粘贴他人的工作。

为什么这是错误的? 认为已经知道一切的想法比任何东西都更能阻碍你学习。这种危险的心态使你对自己的不足视而不见,使你无法处理反馈。结果,你将永远无法进步,你将最终成为职业生涯中一个平庸的程序员,即使你拥有高级或技术总监等头衔。

根本原因是什么? 这种过早的失望的主要原因在于,像 Unity 引擎这样的工具简化了制作游戏的过程,以至于几乎任何人都可以做到。但这意味着很少有人理解他们所使用的工具或编程语言在引擎底层的运作方式。

以为例子,仅仅因为你能够用 C#编写程序,并不意味着你就是 C#专家,但了解语言库的复杂性将使你成为专家。

如何避免这种情况?

以下是一份专业习惯列表,将帮助你避免陷入这种反模式的陷阱:

  • 学习,学习,永远不要停止学习。

  • 避免追求像高级、技术领导或 CTO 这样的头衔,而应专注于真正掌握你的技艺。

  • 每次你使用一个新的工具、库和语言时,尽可能多地研究其起源以及其复杂性。

  • 每天都要谦卑。接受自己并非无所不知,并且成为真正的资深程序员需要几十年的时间。

  • 教学相长,写博客,并在论坛上回答技术问题。换句话说,将你所知的知识传授出去,同时吸收新的信息。这种方法将帮助你验证和构建你的学习。

创业公司给出的职位名称与大公司给出的职位名称并不等价。所以,当你从一个小型独立工作室过渡到 AAA 工作室时,你最终又回到了一个更初级的位置,这并不奇怪。原因是简单的:在大团队中晋升到高级职位更难,因为你需要与更多的程序员竞争更好的职位。

对复杂性的恐惧

“不要害怕完美——你永远达不到。”

  • 拉斐尔·达利

这是什么?

我个人多年来一直受到这种反模式的困扰。这是对简单性总是最好的编程方法的过度热情的结果,因此,你应该避免任何可能看起来稍微复杂一些的解决方案,将其视为阻力最小的路径。

为什么这是不好的?

对复杂性的非理性恐惧可能会阻止你使用复杂和高级的设计模式或算法来解决问题。因此,你减少了成长潜力,限制了学习机会。最终,这可能会阻止你达到成熟和资深水平。

根本原因是什么?

坚信最简单的解决方案是解决任何技术问题的最佳途径。但通常,这只是一个避免进行研究和离开舒适区的借口。

如何避免?

每次你必须决定在简单或复杂解决方案之间做出选择时,你应该问自己以下问题:

  • 我目前是在解决技术挑战中感到投入,还是只是试图完成任务?

  • 我会因为建议一个更高级的解决方案而害怕看起来很愚蠢,因为我并不理解它吗?

  • 我实施的简单解决方案是否会随着时间的推移与当前代码库的整体架构相匹配,或者它只是修补了问题?

因此,总结来说,在决定选择简单或复杂解决方案时,总是问自己这个简单的问题:你是选择最易于访问的方法,因为它是对的,还是因为你只是懒惰,不愿意采用需要更多努力的先进方法?

你经常听到程序员说,复杂性会导致更多的错误。这是真的,但更准确地说,是未管理和误解的复杂性导致了更多的错误。

管理者太多

“我们拥有的管理者没有我们应有的那么多,但我们宁愿少一些,也不愿有太多。”

  • 拉里·佩奇

这是什么?

经理人很棒;他们提供了一个独特的接口,可以访问一组复杂的子系统。由于视频游戏是一个由不断相互交互的系统组成的庞大集合,因此作为接口的管理者可以非常有帮助,有助于减少依赖性。

为什么这是错误的?

如果每个类都是一个管理者,最终会导致管理者之间相互依赖。换句话说,管理者变成了其他管理者的子系统,直到你发现自己处于你试图避免的相同境地,一个依赖性的混乱。另一个负面点是,管理者通常被实现为单例,这意味着你在代码库中散布了全局依赖。

以下是一个代码示例,展示了可能过于依赖 Manager 类的软件架构。如果你在你的源代码中看到类似的情况,你可能需要重构你的架构:

using UnityEngine;

public class GameManager : MonoBehaviour
{
    private Player m_Player;

    void Start()
    {
        // Get the player ID
        m_Player = PlayerManager.Instance.GetPlayer();

        // Sign-in to online services
        OnlineManager.Instance.LoginPlayer(m_Player);

        // Load save game data of the player
        SaveManager.Instance.LoadSaveGame(m_Player);

        // Load player preferred controller configuration
        IInputConfiguration inputConfig = SaveManager.Instance.GetInputConfig(m_Player);
        InputManager.Instance.LoadControllerConfig(inputConfig);
    }
}

根本原因是什么?

根本原因通常是缺乏经验或懒惰的程序员,他们没有考虑代码库的整体架构,而是专注于即时结果。

如何避免这种情况?

这里有一份可能帮助你避免这种反模式的良好习惯列表:

  • 每次你即将使用一个特定的模式时,总是考虑使用一个可能更适合的替代方案。换句话说,避免默认选择最简单的解决方案。

  • 跟踪你的架构和你正在使用的模式。如果你看到标题中包含 Manager 的类太多,就提出一个警告。

  • 如果你正在为你的核心系统实现单元测试时遇到问题,这是一个很好的迹象,表明你的架构中可能存在问题,这可能与有太多的单例或类似全局管理者的类有关。

新的模式,或者现有模式的排列组合,正在定期出现。阅读关于这个主题的新书,以保持对这些模式保持警觉是一个好习惯。

意面代码

“有机建筑寻求优越的使用感,以及更细腻的舒适感,体现在有机的简单性中。”

  • 弗兰克·劳埃德·赖特

那是什么?

意面代码是过度封装和架构被分割成太多单独类别的结果。

为什么这是错误的?

大多数程序员在其职业生涯中听说过意大利面代码这个术语。它通常用来描述无结构和混乱的代码,这种代码通常是由初级程序员产生的。但意面代码可以被认为是相反的;它通常是经验丰富的程序员制作的过度设计的代码的结果,他们缺乏让其他人阅读其工作的愿望。

在这两种情况下,导航和维护受到这些反模式影响的代码库可能会变得困难。

根本原因是什么?

对编程和设计模式采取宗教和教条的态度可能会让你写出看起来准确但对其他人来说却难以阅读的代码。

如何避免这种情况?

这里有一些可能帮助您避免这种反模式的技巧:

  • 在必要时,愿意为了可读性而牺牲准确性

  • 总是考虑设计模式确实为你提供了结构,但通常是以牺牲可读性为代价的

  • 为观众编写代码,并记住可能阅读它的人可能没有与你相同的技能集

大多数专业程序员不会自觉地使用设计模式,通常因为他们不理解它们或者不知道如何正确实现它们。因此,要成为一名优秀的程序员,您必须比其他人更了解所有可用的模式以及如何正确使用它们。

鬼魂

“确实,最好是推迟,以免我们匆忙完成得太少,或者完成它的时间太长。”

  • 特尔图良

那是什么?

鬼魂对象通常是解决临时架构问题的代码的结果,但它们在代码库中保留的时间比应该的要长。

为什么这是错误的?

你必须维护的代码密度通常与你每次进行更改时可能需要修复的 bug 频率有关。另一个副作用是,鬼魂类在你的代码库中徘徊可能会引起对做出更改的恐惧,因为可能会在错误的时间出现未知对象。

根本原因是什么?

可以称为鬼魂的鬼魂对象和类,是良好意图变坏的结果。通常,它们的类被实现来解决临时的架构问题,但程序员从未有机会完成它们的设计,因此你最终会得到内存中存在的对象,但它们存在的原因并不明显。

**如何避免这种情况?**以下是一些可能帮助您避免这种反模式的技巧:

  • 不要使用你不完全理解的设计模式

  • 安排每周代码库审查并删除过时的代码

  • 使用源代码分支策略来管理重要组件的重构

  • 在你的代码中添加 TODO 注释,并要求你的团队定期审查并采取行动

  • 在实施新的架构之前编写文档,以便你的团队可以在你做出更改之前审查你的计划并提供反馈

对于程序员来说,保持极简主义是一种良好的心态。代码可能很复杂,但它永远不应该因为无用的事物而臃肿。始终关注本质,移除非必要的内容。

过早优化

“完美是通过缓慢的步骤实现的;它需要时间的双手。”

  • 伏尔泰

什么是它?

过早优化是在代码需要之前对其进行优化和完美化的行为,因此浪费了宝贵的产品时间。

为什么这是错误的?

在优化上投入比所需更多的时间是浪费时间的一种最糟糕的方式,同时也浪费了雇主的时间。大多数设备每年都在变快,因此程序员越来越不需要优化他们的代码以在有限的硬件上运行得更快。

根本原因是什么?

经验不足通常是根本原因。

如何避免这种情况?

在优化代码之前,始终对其进行性能分析。对于那些可能不知道的人来说,性能分析是使用诊断工具来分析系统性能的行为。通常,你将发现代码中的性能瓶颈仅限于源代码的特定区域,因此通过关注这些区域,你可以提高速度,而无需重构整个代码库。

就像一位优秀的机械师一样,程序员应该有一个工具箱,里面装满了可以帮助他们更快、更好地工作的工具。

供应商锁定

“这不是对技术的信仰。这是对人们的信仰。”

  • 史蒂夫·乔布斯

什么是它?

供应商锁定发生在你开始在代码库中集成第三方组件、插件、框架或库时,但变得依赖于它们以使代码正常工作。

为什么这是错误的?

在 Unity 项目的背景下,依赖于第三方库可能会限制你升级到 Unity 的新版本的能力,因为你可能需要等待供应商的补丁以避免回退。

根本原因是什么? 从第三方供应商购买即插即用的组件和库可以节省大量的生产时间,因此很容易过度依赖它们。

如何避免这种情况? 在购买他们的产品并将它们集成到代码库之前,你应该研究供应商。例如,如果他们没有更新他们的支持论坛,这可能表明他们没有计划在不久的将来发布更新,这可能会限制你在需要时获得即时支持的能力。

作为一名 Unity 开发者,你在编写任何东西之前应该始终检查 Unity 资产商店,因为可能已经有其他人已经以更好的方式完成了你想要做的事情。

按数字管理

这是什么?

按数字管理是指基于由工具(如 Excel 电子表格或报告)生成的统计数据来做出管理决策,而不是基于对项目实际情况的准确分析。

为什么这是错误的?

在生产力报告中表达的数据往往不能反映团队的质量或潜力。它们可能隐藏由动态人际互动引起的问题,而不是揭示它们。这种对数字的关注可能会在关键决策过程中使项目经理失明。换句话说,你能通过程序员一周内修复的 bug 数量来定义程序员的效率吗?答案是,因为特定 bug 的复杂性不是恒定的。你不能用同样的方式评估一周内修复五个简单 bug 的程序员和在同一时期内解决一个但非常复杂的 bug 的程序员。

根本原因是什么?

数字容易解释和证明,尤其是在与没有技术专长、只能通过非常一般的指标评估项目的更高管理层沟通时。这种方法可能导致一个组织花费时间关注数字而不是实际结果。

如何避免这种情况?

高级程序员应该挑战那些使用一般统计数据和数字来评估项目进度的项目经理,通过提供更具体的质量改进指标。以下是一个例子:

  • 服务更新与停机时间

  • 随时间发现的和修复的 bug 数量

在你 40 岁之后,为了确保自己在科技行业有一个长期的职业生涯,最关键的事情之一是回到学校,获得管理学的文凭或证书。这种教育将使你能够过渡到长期领导角色,公司可能会在你积累了数十年经验后鼓励你考虑这一角色。

技术面试

“我选择一个懒惰的人来做一件困难的工作。因为一个懒惰的人会找到一种简单的方法来完成它。”

  • 比尔·盖茨

这是什么?

在程序员招聘过程中,技术面试的概念本身可能听起来不是一个反模式,但我提出它是一个,并且它会对团队生成的源代码质量产生副作用。对于那些从未经历过程序员技术面试的人来说,它涉及一系列测试,这些测试是给候选人以验证他们的技能和知识的。考试可能包括在白板上、一张纸上或在在线测试环境中编写关于编程的答案。我认为技术面试是一个行业范围内的反模式。

为什么这是错误的? 技术面试流程的核心问题是,你只能测试你已经知道的内容。因此,你最终会招聘到与你镜像般的候选人。结果,你将组建一个缺乏各种不同技能的团队。如果您的唯一目标是拥有一个非常专业的团队,这种方法是有效的,但这很少见。大多数公司需要拥有技能多样的员工来平衡组织中的任何弱点。

例如,如果你的技术面试的重点是数据结构,因为这是你作为面试官的优势,那么你可能会淘汰在该领域较弱但在其他领域(如设计模式)较强的候选人。但是,因为你只根据你认为是重要的技术偏见来评估,你可能会错过那些能为你的团队带来新技能的候选人。

根本原因是什么? 我们行业程序员招聘流程之所以如此不一致的主要原因在于,很少有人了解程序员的工作以及如何评估他们作为候选人的能力。因此,招聘经理更倾向于根据候选人的最终技术测试分数来评判他们,从而将候选人的价值简化为一个单一的数字。

此外,还有一些面试官的行为模式或流程也可能是根本原因的一部分:

  • 难题制造者:难题制造者是一种通过提出巧妙谜题来测试候选人技能的面试官。这种方法通常会让大多数候选人感到困惑,并将面试过程变成一场压力游戏。

  • 热椅子:臭名昭著的热椅子面试类型类似于警察审讯,目的是通过快速连续的问题来隔离候选人的弱点和优势。通常,一个面试官会扮演“坏警察”的角色,在提问时更加激进,而另一个则扮演“好警察”的角色,在候选人回答某些问题耗时过长时提供帮助。这种方法最终会耗尽候选人的精力或迫使他们以他们认为面试官想要听到的答案来回答。这不是了解候选人潜力的合适方法。

  • 白板面试:白板面试包括让候选人通过在白板上写下答案来回答技术问题。这种评估候选人的方法存在一个特定的问题;大多数程序员在他们的职业生涯中从未在纸上或白板上编写过代码,所以当在压力情境下(如面试)被迫这样做时,会导致大量关于他们实际技能水平的错误否定。

如何避免这种情况? 几乎每个人都同意,招聘优秀的程序员是一个昂贵且具有挑战性的过程,但这意味着你需要更有创意地处理技术面试,以免拒绝那些只是你团队现有成员复制品的优秀候选人。

这里有一些可以帮助你避免这种反模式的提示:

  • 尝试看看候选人有什么独特和有价值的。找一个能教你和你的团队新东西的候选人。

  • 不要寻找弱点。相反,尝试理解候选人的优势,并看看它们是否与他们的潜在弱点相平衡。

  • 总是考虑到程序员在行业中的技能可能有很多,这取决于他们的专业。例如,平均的网页开发者可能不如 3D 程序员擅长数学,但他们可能在数据库规范化或客户端-服务器应用程序设计方面更出色。

  • 当候选人未能回答一个技术问题时,问问自己这是否是因为他们不理解它,可能没有足够的技能去做,或者可能因为考试过程而过于紧张。换句话说,在评估申请人的实际技能水平时,面试的背景很重要,而不仅仅是最终分数。

即使你是一位经验丰富的专业程序员,你也绝不能低估现代技术面试过程的潜在难度。你的多年经验可能成为一种劣势,因为面试官通常想评估你是否仍然了解你的计算机科学基础知识。换句话说,你可能需要回答一些自大学以来你可能没有复习过的主题的问题。因此,在参加面试之前,翻阅那些旧课本并学习基础知识是个好主意。

摘要

我们已经到达了旅程的终点。在这本书中,我们探讨了各种设计模式,每种模式都有其独特的功能。这本书最重要的收获是,在开始编写代码之前,你应该始终验证是否存在与系统设计意图相匹配的模式。这种方法避免了重复造轮子,并为你提供了一种一致的编程方法,这将有助于你整个职业生涯。

但这一章也揭示了看似合理的设计或管理决策,如果没有意识到其背后的动机和潜在后果,可能会迅速出错。换句话说,作为程序员,我们需要在每个层面上都意识到我们决策的潜在影响,否则我们可能会成为反模式的受害者。

练习

对于我们的最终练习,我为你列出了一个日常习惯清单,这将确保你在游戏行业中作为程序员的长期和成功职业生涯。然而,如果你不逐渐提高你的技能,几乎可以肯定你会陷入平庸,甚至可能变得无关紧要。相信我;这发生在我身上,直到有一天,我决定改变我的习惯,再次专注于真正掌握我的技艺。

这里有一些良好的习惯要养成:

  • 每年至少学习一种新的编程语言。

  • 通过参加练习面试编程考试来定期检查你的技能。

  • 每年获得一项新的技术认证,如 PMP、CCNA 和 CEH。

  • 列出你作为程序员的全部弱点,并每天努力克服它们。

  • 尝试每周至少参加一次与技术相关的聚会活动或会议。

  • 加入技术专业组织,如 ACM 和 IEEE,并使用提供的资源。

  • 通过每天阅读技术和游戏行业新闻来保持对发生的事情的了解。

  • 学习其他领域可能与你的领域相关的课程,包括管理、UI 设计和动画。

  • 为自己列一个与技术和编程相关的博客和 YouTube 频道的清单。每天至少阅读一篇博客文章和观看一个视频。

  • 每月参加一次编码训练营或订阅至少一个培训课程。别忘了完成它们。

  • 每年至少阅读两本关于编程或相关领域的书籍。

  • 开一个 GitHub 账户,并至少为一个开源项目做出贡献,即使只是几行代码。习惯这个过程和社区。

  • 学习冥想;这是一份压力很大的工作;知道如何在压力下保持冷静将有助于你保持心理健康并避免过度劳累。

对于我们的最终练习,我建议你列出你最喜欢的模式,并问问自己为什么喜欢它们。是因为它们易于实现,还是因为它们解决了你代码中的实际架构问题?换句话说,确保你不会因为错误的原因而使用特定的模式,永远不要懒惰,在编写代码时始终保持清醒的选择。

进一步阅读

组织:

博客:

YouTube:

科技新闻:

在线课程:

书籍:

内容概要:本文介绍了一种基于带通滤波后倒谱预白化技术的轴承故障检测方法,特别适用于变速工况下故障特征提取困难的问题。该方法通过对振动信号进行带通滤波,抑制噪声干扰,再利用倒谱预白化消除调制效应,提升周期性冲击特征的可辨识度,最后通过平方包络谱分析有效识别轴承故障频率。文中提供了完整的Matlab代码实现,便于读者复现算法并应用于实际故障诊断场景。该技术对于早期微弱故障信号的检测具有较强敏感性,能够显著提高变速条件下轴承故障诊断的准确性。; 适合人群:具备一定信号处理基础,从事机械故障诊断、工业设备状态监测等相关领域的研究生、科研人员及工程技术人员。; 使用场景及目标:①解决变速工况下传统包络谱分析易受频率混叠和噪声干扰导致故障特征难以识别的问题;②实现对轴承早期故障微弱冲击信号的有效提取与增强;③为旋转机【轴承故障检测】【借助倒谱预白化技术在变速条件下诊断轴承故障的应用】带通滤波后的倒谱预白化的平方包络谱用于轴承故障检测(Matlab代码实现)械的智能运维与预测性维护提供技术支持。; 阅读建议:建议结合Matlab代码逐行理解算法流程,重点关注带通滤波器设计、倒谱预白化处理步骤及平方包络谱的生成过程,同时推荐使用公开数据集(如CWRU)进行验证与对比实验,以深入掌握方法优势与适用边界。
内容概要:本文系统介绍了嵌入式RTOS与Linux系统开发的核心技术,涵盖RTOS的任务管理、优先级设计、同步机制(信号量、互斥锁)、通信机制(消息队列、事件标志组)和定时器管理,以及嵌入式Linux的内核裁剪、设备驱动开发、文件系统与存储管理、网络协议栈应用和多线程开发。文章还探讨了RTOS与Linux混合开发场景下的跨系统通信、任务同步与资源调度策略,并强调了调试与性能优化的重要性,介绍了RTOS和Linux各自的调试工具与方法,旨在帮助开发者构建高性能、高可靠性的嵌入式系统。; 适合人群:具备嵌入式系统基础知识,从事工业控制、物联网、智能设备开发的1-5年经验的软硬件研发工程师;对实时系统与复杂功能集成有需求的技术人员。; 使用场景及目标:①在实时性要求高的场景中合理运用RTOS实现精准任务调度与低延迟响应;②在功能复杂的嵌入式设备中使用Linux进行驱动开发、网络通信与系统扩展;③在双系统架构中实现RTOS与Linux间的高效协同与数据交互;④通过性能分析工具优化系统响应、降低功耗与提升稳定性。; 阅读建议:建议结合实际项目背景阅读,重点关注任务划分、优先级设计、跨系统通信机制及调试工具的使用,配合实验平台动手实践各类驱动、通信接口与性能调优操作,深入理解理论与工程落地之间的联系。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值