diff --git a/src/SignatureAssertionUtil.php b/src/SignatureAssertionUtil.php
new file mode 100644
index 0000000000000000000000000000000000000000..d037290af92a7c0ed40e700f1fab7306772baa61
--- /dev/null
+++ b/src/SignatureAssertionUtil.php
@@ -0,0 +1,112 @@
+<?php declare(strict_types=1);
+/**
+ * This file is part of assert-function-signature.
+ */
+
+namespace Grifart\AssertFunction;
+
+final class SignatureAssertionUtil
+{
+	private function __construct()
+	{
+	}
+
+
+	public static function checkSignature(callable $function, array $parameters, ?string $expectedReturnType): void
+	{
+		$reflection = new \ReflectionFunction($function);
+
+		// NUMBER OF PARAMETERS CHECK:
+		$numberOfParameters = $reflection->getNumberOfParameters();
+		if($numberOfParameters !== count($parameters)) {
+			throw FunctionAssertionError::wrongNumberOrArguments($reflection, count($parameters), $numberOfParameters);
+		}
+
+		// PARAMETER TYPES CHECK:
+		$i = 0;
+		/** @var string[] $parameters */
+		foreach($parameters AS $parameter) {
+			assert(is_string($parameter));
+
+			$parameterReflection = $reflection->getParameters()[$i++];
+			assert($parameterReflection instanceof \ReflectionParameter);
+
+			self::checkFunctionParameter(
+				$reflection,
+				$parameterReflection,
+				...self::parseType($parameter)
+			);
+		}
+
+
+		// RETURN TYPE:
+		if($expectedReturnType !== NULL) {
+			if(!$reflection->hasReturnType()) {
+				throw FunctionAssertionError::missingReturnType($reflection, $expectedReturnType);
+			}
+
+			if ($reflection->hasReturnType()) {
+				list($expectedReturnType, $expectingNullable) = self::parseType($expectedReturnType);
+				$returnTypeReflection = $reflection->getReturnType();
+
+				$actuallyNullable = $returnTypeReflection->allowsNull();
+				if ($expectingNullable !== $actuallyNullable) {
+					throw FunctionAssertionError::wrongReturnTypeNullability(
+						$reflection,
+						$expectingNullable,
+						$actuallyNullable
+					);
+				}
+				if ($expectedReturnType !== (string) $reflection->getReturnType()) {
+					throw FunctionAssertionError::wrongReturnType(
+						$reflection,
+						$expectedReturnType,
+						(string) $reflection->getReturnType()
+					);
+				}
+			}
+		}
+	}
+
+	private static function parseType(string $type): array
+	{
+		$cleanType = $type; // e.g. ?Namespace\Class
+		$optional = FALSE;
+		if ($type[0] === '?') {
+			$optional = TRUE;
+			$cleanType = substr($type, 1); // without leading '?'
+		}
+		return [$cleanType, $optional];
+	}
+
+	private static function checkFunctionParameter(
+		\ReflectionFunction $functionReflection,
+		\ReflectionParameter $parameterReflection,
+		string $expectedParameterType,
+		bool $expectedOptional
+	): void
+	{
+		$reflectionType = $parameterReflection->getType();
+		assert($reflectionType instanceof \ReflectionType);
+
+		// checks:
+		$actuallyOptional = $reflectionType->allowsNull();
+		if ($expectedOptional !== $actuallyOptional) {
+			throw FunctionAssertionError::parameterNullability(
+				$functionReflection,
+				$parameterReflection,
+				$expectedOptional,
+				$actuallyOptional
+			);
+		}
+		if ($expectedParameterType !== (string) $reflectionType) {
+			throw FunctionAssertionError::wrongParameterType(
+				$functionReflection,
+				$parameterReflection,
+				$expectedParameterType,
+				$reflectionType
+			);
+		}
+	}
+
+}
diff --git a/src/exceptions.php b/src/exceptions.php
index dcfe4f9dc827b5b989018a018b6059d85d8761e8..f1d214b8d8737e54f19abe2b65c033808d722b50 100644
--- a/src/exceptions.php
+++ b/src/exceptions.php
@@ -48,4 +48,44 @@ final class FunctionAssertionError extends \AssertionError {
 
 		return implode('/', $pathParts); // normalized shortened readable path
 	}
+
+
+	public static function parameterNullability(\ReflectionFunction $functionReflection, \ReflectionParameter $param, bool $expectedOptional, bool $actuallyOptional): self
+	{
+		return new self(
+			$functionReflection,
+			$expectedOptional
+				? "Function should have parameter {$param->getName()} optional but it is not."
+				: "Function should have parameter {$param->getName()} required but it is not."
+		);
+	}
+
+
+	public static function wrongParameterType(\ReflectionFunction $functionReflection, \ReflectionParameter $parameterReflection, string $expectedParameterType, \ReflectionType $actualParameterType): self
+	{
+		return new self(
+			$functionReflection,
+			"Wrong parameter type given in function {$parameterReflection}. {$expectedParameterType} expected. {$actualParameterType} defined in function."
+		);
+	}
+
+
+	public static function wrongReturnTypeNullability(\ReflectionFunction $reflectionFunction, bool $expectingNullable, bool $actuallyNullable): self
+	{
+		return new self(
+			$reflectionFunction,
+			$expectingNullable
+				? 'Wrong return type nullability. Expecting nullable, but was required.'
+				: 'Wrong return type nullability. Expecting required, but was nullable.'
+		);
+	}
+
+
+	public static function wrongReturnType(\ReflectionFunction $reflection, string $expectedReturnType, string $actualReturnType): self
+	{
+		return new self(
+			$reflection,
+			"Expected return type of type '$expectedReturnType', but function declares '$actualReturnType'"
+		);
+	}
 };
diff --git a/src/functions.php b/src/functions.php
index 7eaf202f4f5e687bb599334eb61c483d13d80e14..54821f76e41c767bc035358282dc4632784dddfa 100644
--- a/src/functions.php
+++ b/src/functions.php
@@ -3,9 +3,7 @@
  * Used to load functions.
  */
 
-// todo: use only assert( ... ) instead of exceptions; to be compatible without configuration with PHP env
-// todo: [performance] separate code itself into autoloaded class
-// todo: better assertion messages + throw FunctionAssertionError + catch in fn + pass it to assert -> compatible with PHP assert config
+// todo: TESTS: better assertion messages + throw FunctionAssertionError + catch in fn + pass it to assert -> compatible with PHP assert config
 // todo: covariance and contra-variance
 
 namespace Grifart\AssertFunction;
@@ -22,71 +20,16 @@ function nullable(string $classType): string
 
 function assertSignature(callable $function, array $parameters, ?string $expectedReturnType): void
 {
-	// todo: call assert(__impl());
-	$reflection = new \ReflectionFunction($function);
-
-	// NUMBER OF PARAMETERS CHECK:
-	$numberOfParameters = $reflection->getNumberOfParameters();
-	assert(
-		$numberOfParameters === count($parameters),
-		FunctionAssertionError::wrongNumberOrArguments($reflection, count($parameters), $numberOfParameters)
-	);
-	if ($numberOfParameters !== count($parameters)) {
-		return;
-	}
-
-
-	// PARAMETER TYPES CHECK:
-	$i = 0;
-	/** @var string[] $parameters */
-	foreach($parameters AS $parameter) {
-		assert(is_string($parameter));
-
-		$parameterReflection = $reflection->getParameters()[$i++];
-		assert($parameterReflection instanceof \ReflectionParameter);
-
-		__checkFunctionParameter($parameterReflection, ...__parseType($parameter)); // todo: move to helper class
-	}
-
-
-	// RETURN TYPE:
-	if($expectedReturnType !== NULL) {
-		assert(
-			$reflection->hasReturnType(),
-			FunctionAssertionError::missingReturnType($reflection, $expectedReturnType)
+	try {
+		SignatureAssertionUtil::checkSignature(
+			$function,
+			$parameters,
+			$expectedReturnType
 		);
 
-		if ($reflection->hasReturnType()) {
-			list($_returnType, $_optional) = __parseType($expectedReturnType);
-			$returnTypeReflection = $reflection->getReturnType();
-
-			assert($_optional === $returnTypeReflection->allowsNull());
-			assert($_returnType === (string) $reflection->getReturnType());
-		}
-	}
-}
-
-function __parseType(string $type): array
-{
-	$cleanType = $type; // e.g. ?Namespace\Class
-	$optional = FALSE;
-	if ($type[0] === '?') {
-		$optional = TRUE;
-		$cleanType = substr($type, 1); // without leading '?'
+	} catch (FunctionAssertionError $error) {
+		// todo: add callee position
+		assert(FALSE, $error);
 	}
-	return [$cleanType, $optional];
-}
-
-function __checkFunctionParameter(
-	\ReflectionParameter $parameterReflection,
-	string $parameterType,
-	bool $optional
-): void
-{
-	$reflectionType = $parameterReflection->getType();
-	assert($reflectionType instanceof \ReflectionType);
 
-	// checks:
-	assert($optional === $reflectionType->allowsNull());
-	assert($parameterType === (string) $reflectionType);
 }