热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

PROGRAMMINGGUIDELINES–PART2:GETTINGRIDOFNULL

PROGRAMMINGGUIDELINES–PART2:GETTINGRIDOFNULL|Inthepreviouspartofthisserieswelookedatwhata

In the previous part of this series we looked at what are basically a lot of guidelines for achieving "clean code". In this part I'd like to take a closer look at something we call null. Our main goal will be: to get rid of it.

THE MEANING OF NULL

Actually, there's not one meaning we can ascribe to null. This is why it complicates our code quite a lot. In the case of null as a return value, it could mean:

  • "I don't know what I should return to you, so I'll return null."
  • "What I'd like to return to you doesn't make sense anyway, so I'll return null."
  • "What you were expecting to find isn't there, so I'll return null."
  • "I never return anything meaningful (because I'm a void function), so I'll returnnull."

I'm sure there are even more situations you can think of.

In the case of null being passed for function arguments, it could mean:

  • "Sorry, I don't know what to provide, so I'll provide null."
  • "It seems this argument is optional, so I'll provide null."
  • "I got this value from someone else (it's null), I'll just pass it along now."

All of this may seem funny, but it's not if your code is actually having these kinds of conversations. It's certainly a good idea to get rid of the uncertainty and vagueness that null brings to your code. Besides, most of the time when you encounter an actual null value in your program, you probably weren't expecting it. You just call a method on it, thinking that it is an object and PHP will rightfully let your program crash.

SOLUTIONS

Every particular null situation requires a different solution, but at least I'll list several common solutions for you. The next time you feel the need for using a null value in your code, try to pick one of the following strategies. The general guideline is to make sure that a value can always be used in the same way.

USE "NULL" EQUIVALENTS

In the case of primitive variables (of type integer, string, boolean, etc.) see if you can use their "null equivalents" as an alternative for returning actual null values:

  • When your function usually returns an array, return an empty array instead ofnull.
  • When your function usually returns an integer, return 0 instead of null.
  • ...

You get the idea. See what makes sense in your case (maybe 0 makes no sense but 1does). If you only supply values of the expected types you can always use the same operators, functions, etc. on any value no matter what. For example, instead of:

if (is_array($values)) {
    foreach ($values as $value) {
        ...
    }
}

you can always do:

foreach ($values as $value) {
    ...
}

PHP (before version 7) only allows you to enforce the correct types of function arguments if their expected types are objects or arrays, in which case passing a nullwon't work. This means that primitive types should still be validated in your function body:

function (ClassName $a, array $b, $c) {
    // $a and $b can't be null, but $c can:
    if (!is_int($c)) {
        throw new \InvalidArgumentException(...);
    }
}

This will of course lead to a lot of extra (duplicate) code, so again I recommend you to use an assertion library, like beberlei/assert:

function (ClassName $a, array $b, $c) {
    Assertion::integer($c);
}

This will prevent many mistakes or implicit type conversions down the road.

USE "NULL" OBJECTS

If you expect a value to be of type array, then retrieving a null value is basically a violation of the contract of that value, since null can't be used in all the ways in which an array can be used. The same is true for objects. If a certain variable was expected to be of a certain class, and instead the variable contains null, the contract was broken. You'll notice this when you call a method on null and PHP crashes with a Fatal error.

In many cases you can prevent this problem by introducing so-called "null objects". Where you would normally return null instead of an object of a given type, you now return another object of the same type (or subtype). This often (but not always) requires the introduction of an interface which serves as the shared type between actual objects and null objects.

Null services

A much seen example is that of optionally injecting a logger in a service-like object or method:

function doSomething(Logger $logger = null) {
    if ($logger !== null) {
        $logger->debug('Start doing something');
    }
    ...
}

The provided logger can be any concrete implementation of the Logger interface. Providing null would be valid, but forces you to check for null anywhere you want to log something. Introducing a "null object" would mean providing another implementation of Logger which does nothing:

class NullLogger implements Logger
{
    public function debug($message)
    {
        // do nothing
    }
}

This works very well since the implementation of all of the methods of such a collaborating service can be left empty: these methods are so-called commandmethods with a void type return value.

Null data objects

It can be a bit more tricky to replace null values in objects that hold interesting data, like domain or view model objects. These objects have (almost) no commandmethods, only query methods, meaning they have an informational return value. In these cases you'll need to provide default or "null" implementations which follow the same contract and offer useful data. Consider the following code, which asks a factory to create a view model for the current user and prints its name:

$userViewModel = $userViewModelFactory->createUserViewModel();
if ($userViewModel === null) {
    echo 'Anonymous user';
} else {
    echo $userViewModel->getDisplayName();
}

We can get rid of the null case by letting the createUserViewModel() always return an object which has the same contract as the user view model for a logged in user:

// shared interface
interface UserViewModel
{
    /**
     * @return string
     */
    public function getDisplayName();
}
// view model for logged in users
class LoggedInUser implements UserViewModel
{
    public function getDisplayName()
    {
        return $this->displayName;
    }
}
// "null object" implementation
class AnonymousUser implements UserViewModel
{
    public function getDisplayName()
    {
        return 'Anonymous user';
    }
}
class UserViewModelFactory
{
    public function createUserViewModel()
    {
        if (/* user is logged in */) {
            return new LoggedInUser(...);
        }
        return new AnonymousUser();
    }
}

Now UserViewModelFactory::createUserViewModel will always return an instance of UserViewModel and client code doesn't have to compensate for a possible null value being returned:

$userViewModel = $userViewModelFactory->createUserViewModel();
echo $userViewModel->getDisplayName();

If the "null object" is a special case of the normal object you may define a base class and extend it, instead of letting both classes implement a shared interface.

THROW EXCEPTIONS

In some cases it isn't possible or rational to return a null-equivalent value or null object. Returning anything else than what the client expects would be a lie. For example in the following class:

class UserRepository
{
    public function getById($id) {
        if (/* user was not found */) {
            return null;
        }
        return new User(...);
    }
}

The null return value here means "I couldn't find what you were looking for". However, the client calling getById() expected to receive a User object (because it expected the provided identifier to be valid). In case that user can't be found, this is exceptional and we need to let the user know what went wrong. In other words, we should throw an exception:

class UserRepository
{
    public function getById($id) {
        if (/* user was not found */) {
            throw UserNotFound::withId($id);
        }
        return new User(...);
    }
}
class UserNotFound extends \RuntimeException
{
    public static function withId($id)
    {
        return new self(
            sprintf(
                'User with id "%s" was not found', $id
            )
        );
    }
}

(See Formatting Exception Messages by Ross Tuck for more information about the above practice of using named constructors and encapsulated message formatting for exceptions.)

ENCAPSULATE NULL

I don't think we can go so far as to completely exterminate null. Reducing the amount of nulls as much as possible is something that benefits your code quality very much already. Whenever you do need to have null values (like in uninitialized attributes), make sure you hide that fact well behind the public interface of your class. In other words: encapsulate. For example, convert a meaningless null into a meaningful boolean:

class Value
{
    private $value;
    public function isDefined() {
        return $this->value !== null;
    }
}

Or make sure that clients don't have to worry about a middle name that's null:

class Name
{
    private $firstName = 'Matthias';
    private $middleName = null;
    private $lastName = 'Noback';
    public function fullName()
    {
        return implode(
            ' ',
            array_filter([
                $this->firstName,
                $this->middleName,
                $this->lastName
            ])
        );
    }
}

When you want to allow your objects to be constructed without certain information, you can hide the fact that you're using nulls in that case by offering alternative named constructors:

class Person
{
    private $name;
    public static function fromName(Name $name)
    {
        $person = new self();
        $person->name = $name;
        return $person;
    }
    public static function anonymous()
    {
        $person = new self();
        // $person->name will remain null
        return $person;
    }
}

CONCLUSION

In this article we've seen several ways in which you can get rid of nulls in your code. When you apply these techniques combined with the ones from the previous article, the complexity of your code will be greatly reduced.

So far we've discussed statements and expressions in function bodies. In the next article we'll take a look at different types of objects and their lifecycles. We'll discuss object creation, references and state changes.

https://www.ibuildings.nl/blog/2016/01/programming-guidelines-php-developers-part-2-getting-rid-null


推荐阅读
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Android JSON基础,音视频开发进阶指南目录
    Array里面的对象数据是有序的,json字符串最外层是方括号的,方括号:[]解析jsonArray代码try{json字符串最外层是 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
author-avatar
耿耿于怀r
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有