Correctly referring to optional child nodes

Godot Version

Godot v4.7.2.stable

Question

What’s a better implementation of referring to possibly non-existant child nodes that doesn’t cause errors?:

To implement different player weapons I’ve got a weapon class extending Node2D.

Each new weapon is its own scene with a weapon node (or node inheriting from weapon) at its root.

Each weapon scene has its own sprite and whatever other children, including a specific Marker2D and/or an AnimationPlayer which the base weapon class refers to, but these are optional.

I have these declared as @onready variables in the base weapon class, and if they’re null certain code is skipped.

This all works fine so far but I’m wondering if there’s a more proper way of referring to these not necessarily extant nodes (once, I know using the $Reference is more expensive) without the debugger throwing errors.

class_name Weapon extends Node2D

@export var weapon_stats : WeaponStats
@onready var gun_barrel: Marker2D = $GunBarrel
@onready var animation_player: AnimationPlayer = $AnimationPlayer

var next_attack = 0

#generic attack, can be overridden for special weapons
func attack() -> bool:
	if Time.get_ticks_msec() < next_attack:
		return false
		
	#if it's a projectile weapon, make the projectiles
	if weapon_stats.projectile_scene != null:
		for i in range(weapon_stats.projectiles_per_shot):
			var bullet = weapon_stats.projectile_scene.instantiate()

			#if a GunBarrel Marker2D is omitted, spawn in the middle of the entity
			if gun_barrel != null:
				bullet.global_position = gun_barrel.global_position
				bullet.global_rotation = gun_barrel.global_rotation
			else:
				bullet.global_position = global_position
				bullet.global_rotation = global_rotation

			get_tree().current_scene.add_child(bullet)

	#re/start attack animation. Animations for weapons are made to last 1 second
	#and scaled based on attack speed
	if animation_player != null:
		animation_player.stop()
		animation_player.play("attack", -1, 1000.0/weapon_stats.refire)
	
	next_attack = Time.get_ticks_msec() + weapon_stats.refire
	return true

Thanks for reading

get_node_or_null is a version of get_node that does not throw errors, $ is shorthand for get_node