Hello, please discuss about error collecting implementation in next PHP
releases
Exceptions have common differences that restrict using them to collect
errors
- Timeloss (trace collection) on call new() on any class that implements
\Throwable - To collect errors and return it to the upper level you have to change
the return signature, so most of the time rewrite the full tree and change
all signatures, that's inapplicable and causes more refactor time without
any benefits - Exceptions will break code, so you need to catch them as much closely as
possible to place you throw it (additional refactoring)
What I want from this feature:
- while I am writing the api, I need not only "log" the errors, but be able
to send all script errors to json output, including warnings, errors, and
deep nested. It will reduce the time of debugging, otherwise i have to
download log files from server or configure external system like sentry
that collects all errors by groups/chains
Suggested solution:
- add non-breakable interface and language construct
raise
to "throw"
error without collecting trace - that error could be any scalar or object, or you can implement new
interface for them, keeping that is nested and taggable array - this
raise
could be catched same way as \Throwable allowing log it
anywhere you need or re-raise
again -
raise
statement won't start to collapse/break code, so it could be
skipped without affecting application
Current solution:
a) 2 classes - ErrorBag/ErrorBagStack.
b) native functions like _error()/_warning() that stores errors to the
current ErrorBag if it exists.
c) ErrorBag exists only if you initialize it:
- from the upper level (you need to collect)
- directly inside a function (you need to decide return/continue depending
on its emptiness) - otherwise _error()/_warning() does nothing
d) once you "catch" results of _error()/_warning() you often need it to
merge the result to the current errorbag, mark it with a nesting group or
with the tag. Nesting group is required once you debug code (you will see
the log), tags will needed once you want to copy results from one bag to
another (closest example - reduce queue with unique function, do action,
then work with initial data)
e) useful feature is merge_as_warning($errorBag) - to prevent the current
error bag to return true
on call ->hasErrors(). Errors are related to low
level function, and possibly you already do actions that allow you just
store them.
f) searching inside the error bag by nesting sequence, or by tag, or by
error type. Error type helps the same as try/catch, tag helps if you want
to save errors to several destinations without memory losses, and nesting
will help most of the time in debugging.
Thanks for your attention.
--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com
On Tue, 6 Feb 2024 at 15:58, Григорий Senior PHP / Разработчик Web <
6562680@gmail.com> wrote:
Hello, please discuss about error collecting implementation in next PHP
releasesExceptions have common differences that restrict using them to collect
errors
- Timeloss (trace collection) on call new() on any class that implements
\Throwable- To collect errors and return it to the upper level you have to change
the return signature, so most of the time rewrite the full tree and change
all signatures, that's inapplicable and causes more refactor time without
any benefits- Exceptions will break code, so you need to catch them as much closely as
possible to place you throw it (additional refactoring)What I want from this feature:
- while I am writing the api, I need not only "log" the errors, but be able
to send all script errors to json output, including warnings, errors, and
deep nested. It will reduce the time of debugging, otherwise i have to
download log files from server or configure external system like sentry
that collects all errors by groups/chainsSuggested solution:
- add non-breakable interface and language construct
raise
to "throw"
error without collecting trace- that error could be any scalar or object, or you can implement new
interface for them, keeping that is nested and taggable array- this
raise
could be catched same way as \Throwable allowing log it
anywhere you need or re-raise
againraise
statement won't start to collapse/break code, so it could be
skipped without affecting applicationCurrent solution:
a) 2 classes - ErrorBag/ErrorBagStack.b) native functions like _error()/_warning() that stores errors to the
current ErrorBag if it exists.c) ErrorBag exists only if you initialize it:
- from the upper level (you need to collect)
- directly inside a function (you need to decide return/continue depending
on its emptiness)- otherwise _error()/_warning() does nothing
d) once you "catch" results of _error()/_warning() you often need it to
merge the result to the current errorbag, mark it with a nesting group or
with the tag. Nesting group is required once you debug code (you will see
the log), tags will needed once you want to copy results from one bag to
another (closest example - reduce queue with unique function, do action,
then work with initial data)e) useful feature is merge_as_warning($errorBag) - to prevent the current
error bag to returntrue
on call ->hasErrors(). Errors are related to low
level function, and possibly you already do actions that allow you just
store them.f) searching inside the error bag by nesting sequence, or by tag, or by
error type. Error type helps the same as try/catch, tag helps if you want
to save errors to several destinations without memory losses, and nesting
will help most of the time in debugging.Thanks for your attention.
--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com
Hello,
This is an application design-level issue, not a language issue.
All you need to do is implement a collector on the logger you use that will
store the info you want and let you ask for that info just before you push
data into the JSON encoder you use from that logger collector. It's as
simple as that, you don't even need to change your existing code if it
already logs the information.
--
Arvīds Godjuks
+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihius
On Tue, 6 Feb 2024 at 16:39, Arvids Godjuks arvids.godjuks@gmail.com
wrote:
On Tue, 6 Feb 2024 at 15:58, Григорий Senior PHP / Разработчик Web <
6562680@gmail.com> wrote:Hello, please discuss about error collecting implementation in next PHP
releasesExceptions have common differences that restrict using them to collect
errors
- Timeloss (trace collection) on call new() on any class that implements
\Throwable- To collect errors and return it to the upper level you have to change
the return signature, so most of the time rewrite the full tree and change
all signatures, that's inapplicable and causes more refactor time without
any benefits- Exceptions will break code, so you need to catch them as much closely
as
possible to place you throw it (additional refactoring)What I want from this feature:
- while I am writing the api, I need not only "log" the errors, but be
able
to send all script errors to json output, including warnings, errors, and
deep nested. It will reduce the time of debugging, otherwise i have to
download log files from server or configure external system like sentry
that collects all errors by groups/chainsSuggested solution:
- add non-breakable interface and language construct
raise
to "throw"
error without collecting trace- that error could be any scalar or object, or you can implement new
interface for them, keeping that is nested and taggable array- this
raise
could be catched same way as \Throwable allowing log it
anywhere you need or re-raise
againraise
statement won't start to collapse/break code, so it could be
skipped without affecting applicationCurrent solution:
a) 2 classes - ErrorBag/ErrorBagStack.b) native functions like _error()/_warning() that stores errors to the
current ErrorBag if it exists.c) ErrorBag exists only if you initialize it:
- from the upper level (you need to collect)
- directly inside a function (you need to decide return/continue depending
on its emptiness)- otherwise _error()/_warning() does nothing
d) once you "catch" results of _error()/_warning() you often need it to
merge the result to the current errorbag, mark it with a nesting group or
with the tag. Nesting group is required once you debug code (you will see
the log), tags will needed once you want to copy results from one bag to
another (closest example - reduce queue with unique function, do action,
then work with initial data)e) useful feature is merge_as_warning($errorBag) - to prevent the current
error bag to returntrue
on call ->hasErrors(). Errors are related to
low
level function, and possibly you already do actions that allow you just
store them.f) searching inside the error bag by nesting sequence, or by tag, or by
error type. Error type helps the same as try/catch, tag helps if you want
to save errors to several destinations without memory losses, and nesting
will help most of the time in debugging.Thanks for your attention.
--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.comHello,
This is an application design-level issue, not a language issue.
All you need to do is implement a collector on the logger you use that
will store the info you want and let you ask for that info just before you
push data into the JSON encoder you use from that logger collector. It's as
simple as that, you don't even need to change your existing code if it
already logs the information.--
Arvīds Godjuks
+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihius
Sending me multiple emails in private with rants is not a behaviour that's
encouraged on this list. Please read the
https://wiki.php.net/email_etiquette_for_people_new_to_php_internals
Arvīds Godjuks
+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihius
Sending you private emails made because "answer" button in Gmail selects
only you to receive.
Sending you private emails that don't even read signs to me you don't need
my answers and have no benefits from reading. But you deny, dont even want
to understand. And notify all subscribers about what you don't want to
understand, and about my mistakes.
It's business-level, capitalism, if you want so, issue. And it sounds like
"we won't pay you for your mistakes".
There's no magic pill that accidentally forces business to understand you
now need errors collection and it needs at least two more days for
"design-level" refactoring without business benefit. Also no magic pills to
force the business owner to give you that 2 days before you face that
requirement (you know, deadlines stuff).
Refactoring should be reduced by time as much as possible otherwise you
feel more stress on your job. I have no goal to convince you that this is
necessary, I solved this problem for myself. But at the language level it
can be solved more conveniently and better, and you will also benefit from
this.
All languages work as a stack. So errors could be collected as a stack on
language level. Directly in place you need that stack instead of
everywhere. Also forcing to close applications is safe-shield, instead of
validation errors that could be found anywhere. PHP devs in most even
imagined a solution that "you have to do validation as much earlier as you
can". But... remote api responses still become from inside to outside,
remote api requirements that accidentally arrive still become once your
code is ready, and unpredictable.
That simple enhancement allows you to implement error collection and
dont touch your methods and signatures. Nice and easy. Or you can believe
in `truth` and deny ideas because it does not correlate with your
principles.
вт, 6 февр. 2024 г. в 17:56, Arvids Godjuks arvids.godjuks@gmail.com:
On Tue, 6 Feb 2024 at 16:39, Arvids Godjuks arvids.godjuks@gmail.com
wrote:On Tue, 6 Feb 2024 at 15:58, Григорий Senior PHP / Разработчик Web <
6562680@gmail.com> wrote:Hello, please discuss about error collecting implementation in next PHP
releasesExceptions have common differences that restrict using them to collect
errors
- Timeloss (trace collection) on call new() on any class that implements
\Throwable- To collect errors and return it to the upper level you have to change
the return signature, so most of the time rewrite the full tree and
change
all signatures, that's inapplicable and causes more refactor time without
any benefits- Exceptions will break code, so you need to catch them as much closely
as
possible to place you throw it (additional refactoring)What I want from this feature:
- while I am writing the api, I need not only "log" the errors, but be
able
to send all script errors to json output, including warnings, errors, and
deep nested. It will reduce the time of debugging, otherwise i have to
download log files from server or configure external system like sentry
that collects all errors by groups/chainsSuggested solution:
- add non-breakable interface and language construct
raise
to "throw"
error without collecting trace- that error could be any scalar or object, or you can implement new
interface for them, keeping that is nested and taggable array- this
raise
could be catched same way as \Throwable allowing log it
anywhere you need or re-raise
againraise
statement won't start to collapse/break code, so it could be
skipped without affecting applicationCurrent solution:
a) 2 classes - ErrorBag/ErrorBagStack.b) native functions like _error()/_warning() that stores errors to the
current ErrorBag if it exists.c) ErrorBag exists only if you initialize it:
- from the upper level (you need to collect)
- directly inside a function (you need to decide return/continue
depending
on its emptiness)- otherwise _error()/_warning() does nothing
d) once you "catch" results of _error()/_warning() you often need it to
merge the result to the current errorbag, mark it with a nesting group or
with the tag. Nesting group is required once you debug code (you will see
the log), tags will needed once you want to copy results from one bag to
another (closest example - reduce queue with unique function, do action,
then work with initial data)e) useful feature is merge_as_warning($errorBag) - to prevent the current
error bag to returntrue
on call ->hasErrors(). Errors are related to
low
level function, and possibly you already do actions that allow you just
store them.f) searching inside the error bag by nesting sequence, or by tag, or by
error type. Error type helps the same as try/catch, tag helps if you want
to save errors to several destinations without memory losses, and nesting
will help most of the time in debugging.Thanks for your attention.
--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ
связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.comHello,
This is an application design-level issue, not a language issue.
All you need to do is implement a collector on the logger you use that
will store the info you want and let you ask for that info just before you
push data into the JSON encoder you use from that logger collector. It's as
simple as that, you don't even need to change your existing code if it
already logs the information.--
Arvīds Godjuks
+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihiusSending me multiple emails in private with rants is not a behaviour that's
encouraged on this list. Please read the
https://wiki.php.net/email_etiquette_for_people_new_to_php_internalsArvīds Godjuks
+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihius
--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com
On Wed, Feb 7, 2024 at 12:00 AM Григорий Senior PHP / Разработчик Web
6562680@gmail.com wrote:
Sending you private emails made because "answer" button in Gmail selects
only you to receive.
Sending you private emails that don't even read signs to me you don't need
my answers and have no benefits from reading. But you deny, dont even want
to understand. And notify all subscribers about what you don't want to
understand, and about my mistakes.It's business-level, capitalism, if you want so, issue. And it sounds like "we won't pay you for your mistakes". There's no magic pill that accidentally forces business to understand you now need errors collection and it needs at least two more days for "design-level" refactoring without business benefit. Also no magic pills to force the business owner to give you that 2 days before you face that requirement (you know, deadlines stuff). Refactoring should be reduced by time as much as possible otherwise you feel more stress on your job. I have no goal to convince you that this is necessary, I solved this problem for myself. But at the language level it can be solved more conveniently and better, and you will also benefit from this. All languages work as a stack. So errors could be collected as a stack on language level. Directly in place you need that stack instead of everywhere. Also forcing to close applications is safe-shield, instead of validation errors that could be found anywhere. PHP devs in most even imagined a solution that "you have to do validation as much earlier as you can". But... remote api responses still become from inside to outside, remote api requirements that accidentally arrive still become once your code is ready, and unpredictable. That simple enhancement allows you to implement error collection and dont touch your methods and signatures. Nice and easy. Or you can believe in `truth` and deny ideas because it does not correlate with your principles.
Hey friend, easy :)
You was correctly pointed out that this is an application-level issue,
not a language one. It is your responsibility to collect anything you
want and deal with the business when it puts some requirements. Did
you ever experience out-of-memory issues because something in your
application is collecting some stuff and don't dispose it? Did you
ever work with algorithms running iterations over millions of records
in PHP? Can you imagine a language-level bomb if PHP will collect
anything in its own memory in every iteration of this kind and not
dispose it during the whole request?
You don't need to reply to me, just take a deep breath and think about
it. I believe this kind of discussions is an off-topic in this mailing
list.
--
Best, Alexander.
вт, 6 февр. 2024 г. в 17:56, Arvids Godjuks <arvids.godjuks@gmail.com>: > > > On Tue, 6 Feb 2024 at 16:39, Arvids Godjuks <arvids.godjuks@gmail.com> > wrote: > >> >> >> On Tue, 6 Feb 2024 at 15:58, Григорий Senior PHP / Разработчик Web < >> 6562680@gmail.com> wrote: >> >>> Hello, please discuss about error collecting implementation in next PHP >>> releases >>> >>> Exceptions have common differences that restrict using them to collect >>> errors >>> 1. Timeloss (trace collection) on call new() on any class that implements >>> \Throwable >>> 2. To collect errors and return it to the upper level you have to change >>> the return signature, so most of the time rewrite the full tree and >>> change >>> all signatures, that's inapplicable and causes more refactor time without >>> any benefits >>> 3. Exceptions will break code, so you need to catch them as much closely >>> as >>> possible to place you throw it (additional refactoring) >>> >>> What I want from this feature: >>> - while I am writing the api, I need not only "log" the errors, but be >>> able >>> to send all script errors to json output, including warnings, errors, and >>> deep nested. It will reduce the time of debugging, otherwise i have to >>> download log files from server or configure external system like sentry >>> that collects all errors by groups/chains >>> >>> Suggested solution: >>> - add non-breakable interface and language construct `raise` to "throw" >>> error without collecting trace >>> - that error could be any scalar or object, or you can implement new >>> interface for them, keeping that is nested and taggable array >>> - this `raise` could be catched same way as \Throwable allowing log it >>> anywhere you need or re-`raise` again >>> - `raise` statement won't start to collapse/break code, so it could be >>> skipped without affecting application >>> >>> Current solution: >>> a) 2 classes - ErrorBag/ErrorBagStack. >>> >>> b) native functions like _error()/_warning() that stores errors to the >>> current ErrorBag if it exists. >>> >>> c) ErrorBag exists only if you initialize it: >>> - from the upper level (you need to collect) >>> - directly inside a function (you need to decide return/continue >>> depending >>> on its emptiness) >>> - otherwise _error()/_warning() does nothing >>> >>> d) once you "catch" results of _error()/_warning() you often need it to >>> merge the result to the current errorbag, mark it with a nesting group or >>> with the tag. Nesting group is required once you debug code (you will see >>> the log), tags will needed once you want to copy results from one bag to >>> another (closest example - reduce queue with unique function, do action, >>> then work with initial data) >>> >>> e) useful feature is merge_as_warning($errorBag) - to prevent the current >>> error bag to return `true` on call ->hasErrors(). Errors are related to >>> low >>> level function, and possibly you already do actions that allow you just >>> store them. >>> >>> f) searching inside the error bag by nesting sequence, or by tag, or by >>> error type. Error type helps the same as try/catch, tag helps if you want >>> to save errors to several destinations without memory losses, and nesting >>> will help most of the time in debugging. >>> >>> Thanks for your attention. >>> >>> -- >>> +375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ >>> связи >>> https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram >>> 6562680@gmail.com >>> >> >> Hello, >> >> This is an application design-level issue, not a language issue. >> All you need to do is implement a collector on the logger you use that >> will store the info you want and let you ask for that info just before you >> push data into the JSON encoder you use from that logger collector. It's as >> simple as that, you don't even need to change your existing code if it >> already logs the information. >> >> -- >> >> Arvīds Godjuks >> +371 26 851 664 >> arvids.godjuks@gmail.com >> Telegram: @psihius https://t.me/psihius >> > > Sending me multiple emails in private with rants is not a behaviour that's > encouraged on this list. Please read the > https://wiki.php.net/email_etiquette_for_people_new_to_php_internals > -- > > Arvīds Godjuks > +371 26 851 664 > arvids.godjuks@gmail.com > Telegram: @psihius https://t.me/psihius > -- +375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ связи https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram 6562680@gmail.com
Short answer is yes. Glad to see that personally adapted answer.
That's why in the relevant github issue i show how to collect ONLY if you
need.
If you initialize the error bag - it collects, if not - it skips. T
So the try/catch
statement outside means you initialized, also a special
decorator or additional command could initiate the raise collector right in
the function !
You collect only the cases you want to collect. That's the difference.
вт, 6 февр. 2024 г. в 18:20, Alexander Pravdin alex.pravdin@interi.co:
On Wed, Feb 7, 2024 at 12:00 AM Григорий Senior PHP / Разработчик Web
6562680@gmail.com wrote:Sending you private emails made because "answer" button in Gmail selects
only you to receive.
Sending you private emails that don't even read signs to me you don't
need
my answers and have no benefits from reading. But you deny, dont even
want
to understand. And notify all subscribers about what you don't want to
understand, and about my mistakes.It's business-level, capitalism, if you want so, issue. And it sounds
like
"we won't pay you for your mistakes".
There's no magic pill that accidentally forces business to understand you
now need errors collection and it needs at least two more days for
"design-level" refactoring without business benefit. Also no magic pills
to
force the business owner to give you that 2 days before you face that
requirement (you know, deadlines stuff).Refactoring should be reduced by time as much as possible otherwise you
feel more stress on your job. I have no goal to convince you that this is
necessary, I solved this problem for myself. But at the language level it
can be solved more conveniently and better, and you will also benefit
from
this.All languages work as a stack. So errors could be collected as a stack on
language level. Directly in place you need that stack instead of
everywhere. Also forcing to close applications is safe-shield, instead of
validation errors that could be found anywhere. PHP devs in most even
imagined a solution that "you have to do validation as much earlier as
you
can". But... remote api responses still become from inside to outside,
remote api requirements that accidentally arrive still become once your
code is ready, and unpredictable.That simple enhancement allows you to implement error collection and
dont touch your methods and signatures. Nice and easy. Or you can believe
intruth
and deny ideas because it does not correlate with your
principles.Hey friend, easy :)
You was correctly pointed out that this is an application-level issue,
not a language one. It is your responsibility to collect anything you
want and deal with the business when it puts some requirements. Did
you ever experience out-of-memory issues because something in your
application is collecting some stuff and don't dispose it? Did you
ever work with algorithms running iterations over millions of records
in PHP? Can you imagine a language-level bomb if PHP will collect
anything in its own memory in every iteration of this kind and not
dispose it during the whole request?You don't need to reply to me, just take a deep breath and think about
it. I believe this kind of discussions is an off-topic in this mailing
list.--
Best, Alexander.вт, 6 февр. 2024 г. в 17:56, Arvids Godjuks <arvids.godjuks@gmail.com>: > > > On Tue, 6 Feb 2024 at 16:39, Arvids Godjuks <arvids.godjuks@gmail.com> > wrote: > >> >> >> On Tue, 6 Feb 2024 at 15:58, Григорий Senior PHP / Разработчик Web < >> 6562680@gmail.com> wrote: >> >>> Hello, please discuss about error collecting implementation in next
PHP
releases
Exceptions have common differences that restrict using them to
collect
errors
- Timeloss (trace collection) on call new() on any class that
implements
\Throwable- To collect errors and return it to the upper level you have to
change
the return signature, so most of the time rewrite the full tree and
change
all signatures, that's inapplicable and causes more refactor time
without
any benefits- Exceptions will break code, so you need to catch them as much
closely
as
possible to place you throw it (additional refactoring)What I want from this feature:
- while I am writing the api, I need not only "log" the errors, but
be
able
to send all script errors to json output, including warnings,
errors, and
deep nested. It will reduce the time of debugging, otherwise i have
to
download log files from server or configure external system like
sentry
that collects all errors by groups/chainsSuggested solution:
- add non-breakable interface and language construct
raise
to
"throw"
error without collecting trace- that error could be any scalar or object, or you can implement new
interface for them, keeping that is nested and taggable array- this
raise
could be catched same way as \Throwable allowing log
it
anywhere you need or re-raise
againraise
statement won't start to collapse/break code, so it could
be
skipped without affecting applicationCurrent solution:
a) 2 classes - ErrorBag/ErrorBagStack.b) native functions like _error()/_warning() that stores errors to
the
current ErrorBag if it exists.c) ErrorBag exists only if you initialize it:
- from the upper level (you need to collect)
- directly inside a function (you need to decide return/continue
depending
on its emptiness)- otherwise _error()/_warning() does nothing
d) once you "catch" results of _error()/_warning() you often need it
to
merge the result to the current errorbag, mark it with a nesting
group or
with the tag. Nesting group is required once you debug code (you
will see
the log), tags will needed once you want to copy results from one
bag to
another (closest example - reduce queue with unique function, do
action,
then work with initial data)e) useful feature is merge_as_warning($errorBag) - to prevent the
current
error bag to returntrue
on call ->hasErrors(). Errors are related
to
low
level function, and possibly you already do actions that allow you
just
store them.f) searching inside the error bag by nesting sequence, or by tag, or
by
error type. Error type helps the same as try/catch, tag helps if you
want
to save errors to several destinations without memory losses, and
nesting
will help most of the time in debugging.Thanks for your attention.
--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ
связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.comHello,
This is an application design-level issue, not a language issue.
All you need to do is implement a collector on the logger you use that
will store the info you want and let you ask for that info just
before you
push data into the JSON encoder you use from that logger collector.
It's as
simple as that, you don't even need to change your existing code if it
already logs the information.--
Arvīds Godjuks
+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihiusSending me multiple emails in private with rants is not a behaviour
that's
encouraged on this list. Please read the
https://wiki.php.net/email_etiquette_for_people_new_to_php_internalsArvīds Godjuks
+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihius--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ
связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com
--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com
On Tue, Feb 6, 2024 at 5:26 PM Григорий Senior PHP / Разработчик Web <
6562680@gmail.com> wrote:
Short answer is yes. Glad to see that personally adapted answer.
What are those languages specifically?
On Tue, Feb 6, 2024 at 4:26 PM Григорий Senior PHP / Разработчик Web
6562680@gmail.com wrote:
Short answer is yes. Glad to see that personally adapted answer.
That's why in the relevant github issue i show how to collect ONLY if you
need.
If you initialize the error bag - it collects, if not - it skips. TSo the
try/catch
statement outside means you initialized, also a special
decorator or additional command could initiate the raise collector right in
the function !You collect only the cases you want to collect. That's the difference.
Tue, Feb 6 2024 at 18:20, Alexander Pravdin alex.pravdin@interi.co:
On Wed, Feb 7, 2024 at 12:00 AM Григорий Senior PHP / Разработчик Web
6562680@gmail.com wrote:Sending you private emails made because "answer" button in Gmail selects
only you to receive.
Sending you private emails that don't even read signs to me you don't
need
my answers and have no benefits from reading. But you deny, dont even
want
to understand. And notify all subscribers about what you don't want to
understand, and about my mistakes.It's business-level, capitalism, if you want so, issue. And it sounds
like
"we won't pay you for your mistakes".
There's no magic pill that accidentally forces business to understand you
now need errors collection and it needs at least two more days for
"design-level" refactoring without business benefit. Also no magic pills
to
force the business owner to give you that 2 days before you face that
requirement (you know, deadlines stuff).Refactoring should be reduced by time as much as possible otherwise you
feel more stress on your job. I have no goal to convince you that this is
necessary, I solved this problem for myself. But at the language level it
can be solved more conveniently and better, and you will also benefit
from
this.All languages work as a stack. So errors could be collected as a stack on
language level. Directly in place you need that stack instead of
everywhere. Also forcing to close applications is safe-shield, instead of
validation errors that could be found anywhere. PHP devs in most even
imagined a solution that "you have to do validation as much earlier as
you
can". But... remote api responses still become from inside to outside,
remote api requirements that accidentally arrive still become once your
code is ready, and unpredictable.That simple enhancement allows you to implement error collection and
dont touch your methods and signatures. Nice and easy. Or you can believe
intruth
and deny ideas because it does not correlate with your
principles.Hey friend, easy :)
You was correctly pointed out that this is an application-level issue,
not a language one. It is your responsibility to collect anything you
want and deal with the business when it puts some requirements. Did
you ever experience out-of-memory issues because something in your
application is collecting some stuff and don't dispose it? Did you
ever work with algorithms running iterations over millions of records
in PHP? Can you imagine a language-level bomb if PHP will collect
anything in its own memory in every iteration of this kind and not
dispose it during the whole request?You don't need to reply to me, just take a deep breath and think about
it. I believe this kind of discussions is an off-topic in this mailing
list.--
Best, Alexander.вт, 6 февр. 2024 г. в 17:56, Arvids Godjuks <arvids.godjuks@gmail.com>: > > > On Tue, 6 Feb 2024 at 16:39, Arvids Godjuks <arvids.godjuks@gmail.com> > wrote: > >> >> >> On Tue, 6 Feb 2024 at 15:58, Григорий Senior PHP / Разработчик Web < >> 6562680@gmail.com> wrote: >> >>> Hello, please discuss about error collecting implementation in next
PHP
releases
Exceptions have common differences that restrict using them to
collect
errors
- Timeloss (trace collection) on call new() on any class that
implements
\Throwable- To collect errors and return it to the upper level you have to
change
the return signature, so most of the time rewrite the full tree and
change
all signatures, that's inapplicable and causes more refactor time
without
any benefits- Exceptions will break code, so you need to catch them as much
closely
as
possible to place you throw it (additional refactoring)What I want from this feature:
- while I am writing the api, I need not only "log" the errors, but
be
able
to send all script errors to json output, including warnings,
errors, and
deep nested. It will reduce the time of debugging, otherwise i have
to
download log files from server or configure external system like
sentry
that collects all errors by groups/chainsSuggested solution:
- add non-breakable interface and language construct
raise
to
"throw"
error without collecting trace- that error could be any scalar or object, or you can implement new
interface for them, keeping that is nested and taggable array- this
raise
could be catched same way as \Throwable allowing log
it
anywhere you need or re-raise
againraise
statement won't start to collapse/break code, so it could
be
skipped without affecting applicationCurrent solution:
a) 2 classes - ErrorBag/ErrorBagStack.b) native functions like _error()/_warning() that stores errors to
the
current ErrorBag if it exists.c) ErrorBag exists only if you initialize it:
- from the upper level (you need to collect)
- directly inside a function (you need to decide return/continue
depending
on its emptiness)- otherwise _error()/_warning() does nothing
d) once you "catch" results of _error()/_warning() you often need it
to
merge the result to the current errorbag, mark it with a nesting
group or
with the tag. Nesting group is required once you debug code (you
will see
the log), tags will needed once you want to copy results from one
bag to
another (closest example - reduce queue with unique function, do
action,
then work with initial data)e) useful feature is merge_as_warning($errorBag) - to prevent the
current
error bag to returntrue
on call ->hasErrors(). Errors are related
to
low
level function, and possibly you already do actions that allow you
just
store them.f) searching inside the error bag by nesting sequence, or by tag, or
by
error type. Error type helps the same as try/catch, tag helps if you
want
to save errors to several destinations without memory losses, and
nesting
will help most of the time in debugging.Thanks for your attention.
--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ
связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.comHello,
This is an application design-level issue, not a language issue.
All you need to do is implement a collector on the logger you use that
will store the info you want and let you ask for that info just
before you
push data into the JSON encoder you use from that logger collector.
It's as
simple as that, you don't even need to change your existing code if it
already logs the information.--
Arvīds Godjuks
+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihiusSending me multiple emails in private with rants is not a behaviour
that's
encouraged on this list. Please read the
https://wiki.php.net/email_etiquette_for_people_new_to_php_internalsArvīds Godjuks
+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihius--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ
связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com
I recommend looking at how other applications handle this. For
example, WordPress uses the WP_Error class to collect errors and
returns ProperReturnType|WP_Error, and error handling is very similar
to Go. Symfony validations simply collect validation errors and an
empty array is "all good." Both approaches are totally fine, but it
depends on what you are actually trying to accomplish.
Robert Landers
Software Engineer
Utrecht NL
On Tue, Feb 6, 2024 at 3:58 PM Григорий Senior PHP / Разработчик Web <
6562680@gmail.com> wrote:
- add non-breakable interface and language construct
raise
to "throw"
error without collecting trace- that error could be any scalar or object, or you can implement new
interface for them, keeping that is nested and taggable array- this
raise
could be catched same way as \Throwable allowing log it
anywhere you need or re-raise
againraise
statement won't start to collapse/break code, so it could be
skipped without affecting application
Is there an existing language that does that, having both exceptions and
these silent raise statements?
Javascript is closer to.
It allows you to throw anything, but it is still the throw statement,
keeping in the mind the async nature of js - memory and processor stuff is
shared by the time.
JS seniors usually hate those guys who throw anything except language Error
class because they skipped the mandatory level of programming - OOP. They
are now taking fun from it. We're tired of OOP for now. Once you work with
batches/queue/bulks you need pipelines and chaining, and there's a throw
works only to stop any certain tasks and almost immediately catch the next
line. So throw
is required to be safe-shield, but solves not enough count
of cases.
Old, maybe 10 years ago, Fowler's article about "errors is not an
exception". He explained why, but recommend to implement own error bag. I
tried few times implement own error bag on production ready code. And this
is the hell of rewriting full nesting tree and carrying that return
statement to upper level again and again, then you start to get confused,
then you rewrite all return to objects with properties/getters/setters,
then you understand your PHPStorm started to lag because of 70 uses in
class... Better to use global error bag stack that you can enable or
disable for your needs outside function ("in controller", GRASP) or inside
function directly like old good times $errors[] and if ($errors) { return
null; }
вт, 6 февр. 2024 г. в 18:23, Alex Wells autaut03@gmail.com:
On Tue, Feb 6, 2024 at 3:58 PM Григорий Senior PHP / Разработчик Web <
6562680@gmail.com> wrote:
- add non-breakable interface and language construct
raise
to "throw"
error without collecting trace- that error could be any scalar or object, or you can implement new
interface for them, keeping that is nested and taggable array- this
raise
could be catched same way as \Throwable allowing log it
anywhere you need or re-raise
againraise
statement won't start to collapse/break code, so it could be
skipped without affecting applicationIs there an existing language that does that, having both exceptions and
these silent raise statements?
--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com
JavaScript is JavaScript - it's not a good role model to look at. If
anything, JavaScript is a collection of things of how not to design a
language :)
What you are looking for is Golang.
The level of changes you are proposing require it to go thriugh an RFC
process, have 2/3rds of voters to agree to it. Tland that is after a
feasibility study is even done - engine might not even allow to implement
such thi g and require exte sive modifications for a thing that should be
done on application level to begin with.
And memory usage is one of the biggest points against it - engine allowing
to store I side it arbitrary data that is logged by application on a
per-request level is just a bad idea. People will shove megabytes of logs
into it in a loop and them file reports "why is php using 2 GB of RAM?" -
this is literally a daily question you get woth relation to Doctrine when
people try to run bulk operations, do not disable trace logger and them run
into the memory limit.
What you are proposing is a footgun at it's finest and PHP has a rich
history of those and we have learned from the experience as a community.
Things like this are left to the userland. There are many libraries that
help handle this.
On Tue, Feb 6, 2024, 17:35 Григорий Senior PHP / Разработчик Web <
6562680@gmail.com> wrote:
Javascript is closer to.
It allows you to throw anything, but it is still the throw statement,
keeping in the mind the async nature of js - memory and processor stuff is
shared by the time.JS seniors usually hate those guys who throw anything except language Error
class because they skipped the mandatory level of programming - OOP. They
are now taking fun from it. We're tired of OOP for now. Once you work with
batches/queue/bulks you need pipelines and chaining, and there's a throw
works only to stop any certain tasks and almost immediately catch the next
line. Sothrow
is required to be safe-shield, but solves not enough count
of cases.Old, maybe 10 years ago, Fowler's article about "errors is not an
exception". He explained why, but recommend to implement own error bag. I
tried few times implement own error bag on production ready code. And this
is the hell of rewriting full nesting tree and carrying that return
statement to upper level again and again, then you start to get confused,
then you rewrite all return to objects with properties/getters/setters,
then you understand your PHPStorm started to lag because of 70 uses in
class... Better to use global error bag stack that you can enable or
disable for your needs outside function ("in controller", GRASP) or inside
function directly like old good times $errors[] and if ($errors) { return
null; }вт, 6 февр. 2024 г. в 18:23, Alex Wells autaut03@gmail.com:
On Tue, Feb 6, 2024 at 3:58 PM Григорий Senior PHP / Разработчик Web <
6562680@gmail.com> wrote:
- add non-breakable interface and language construct
raise
to "throw"
error without collecting trace- that error could be any scalar or object, or you can implement new
interface for them, keeping that is nested and taggable array- this
raise
could be catched same way as \Throwable allowing log it
anywhere you need or re-raise
againraise
statement won't start to collapse/break code, so it could be
skipped without affecting applicationIs there an existing language that does that, having both exceptions and
these silent raise statements?--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com
My function seems like this:
_error_bag_error(error) {
if (stack.errorBag) {
stack.errorBag.add(error);
}
}
It does nothing if i didn't initialize the error bag manually.
I should call _error_bag() inside the current function to create one in the
stack, or _error_bag_push() (and then _error_bag_pop()) outside the
function to collect children.
Doctrine's main problem is the dreadnought that throws low level exceptions
and forces developers to spend weeks to understand "wow, that's the way it
should be". Funny but painful. For one small benefit - reducing the count
of queries by unique insertions/deletions, maybe 10% of queries are removed.
вт, 6 февр. 2024 г. в 18:54, Arvids Godjuks arvids.godjuks@gmail.com:
JavaScript is JavaScript - it's not a good role model to look at. If
anything, JavaScript is a collection of things of how not to design a
language :)What you are looking for is Golang.
The level of changes you are proposing require it to go thriugh an RFC
process, have 2/3rds of voters to agree to it. Tland that is after a
feasibility study is even done - engine might not even allow to implement
such thi g and require exte sive modifications for a thing that should be
done on application level to begin with.And memory usage is one of the biggest points against it - engine allowing
to store I side it arbitrary data that is logged by application on a
per-request level is just a bad idea. People will shove megabytes of logs
into it in a loop and them file reports "why is php using 2 GB of RAM?" -
this is literally a daily question you get woth relation to Doctrine when
people try to run bulk operations, do not disable trace logger and them run
into the memory limit.What you are proposing is a footgun at it's finest and PHP has a rich
history of those and we have learned from the experience as a community.
Things like this are left to the userland. There are many libraries that
help handle this.On Tue, Feb 6, 2024, 17:35 Григорий Senior PHP / Разработчик Web <
6562680@gmail.com> wrote:Javascript is closer to.
It allows you to throw anything, but it is still the throw statement,
keeping in the mind the async nature of js - memory and processor stuff is
shared by the time.JS seniors usually hate those guys who throw anything except language
Error
class because they skipped the mandatory level of programming - OOP. They
are now taking fun from it. We're tired of OOP for now. Once you work with
batches/queue/bulks you need pipelines and chaining, and there's a throw
works only to stop any certain tasks and almost immediately catch the next
line. Sothrow
is required to be safe-shield, but solves not enough
count
of cases.Old, maybe 10 years ago, Fowler's article about "errors is not an
exception". He explained why, but recommend to implement own error bag. I
tried few times implement own error bag on production ready code. And this
is the hell of rewriting full nesting tree and carrying that return
statement to upper level again and again, then you start to get confused,
then you rewrite all return to objects with properties/getters/setters,
then you understand your PHPStorm started to lag because of 70 uses in
class... Better to use global error bag stack that you can enable or
disable for your needs outside function ("in controller", GRASP) or inside
function directly like old good times $errors[] and if ($errors) { return
null; }вт, 6 февр. 2024 г. в 18:23, Alex Wells autaut03@gmail.com:
On Tue, Feb 6, 2024 at 3:58 PM Григорий Senior PHP / Разработчик Web <
6562680@gmail.com> wrote:
- add non-breakable interface and language construct
raise
to "throw"
error without collecting trace- that error could be any scalar or object, or you can implement new
interface for them, keeping that is nested and taggable array- this
raise
could be catched same way as \Throwable allowing log it
anywhere you need or re-raise
againraise
statement won't start to collapse/break code, so it could be
skipped without affecting applicationIs there an existing language that does that, having both exceptions and
these silent raise statements?--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com
--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com
Btw, i agree about Javascript, but on a low level it produces the most
clean code, because there's no types and rules. All types moved to
TypeScript's client side compiler.
JS 15 years ago ACCIDENTALLY created a pipeline. Named it "Promise". We
spent years after to understand that while (true) and then/catch should be
different patterns.
вт, 6 февр. 2024 г. в 19:08, Григорий Senior PHP / Разработчик Web <
6562680@gmail.com>:
My function seems like this:
_error_bag_error(error) { if (stack.errorBag) { stack.errorBag.add(error); } }
It does nothing if i didn't initialize the error bag manually.
I should call _error_bag() inside the current function to create one in
the stack, or _error_bag_push() (and then _error_bag_pop()) outside the
function to collect children.Doctrine's main problem is the dreadnought that throws low level
exceptions and forces developers to spend weeks to understand "wow, that's
the way it should be". Funny but painful. For one small benefit - reducing
the count of queries by unique insertions/deletions, maybe 10% of queries
are removed.вт, 6 февр. 2024 г. в 18:54, Arvids Godjuks arvids.godjuks@gmail.com:
JavaScript is JavaScript - it's not a good role model to look at. If
anything, JavaScript is a collection of things of how not to design a
language :)What you are looking for is Golang.
The level of changes you are proposing require it to go thriugh an RFC
process, have 2/3rds of voters to agree to it. Tland that is after a
feasibility study is even done - engine might not even allow to implement
such thi g and require exte sive modifications for a thing that should be
done on application level to begin with.And memory usage is one of the biggest points against it - engine
allowing to store I side it arbitrary data that is logged by application on
a per-request level is just a bad idea. People will shove megabytes of logs
into it in a loop and them file reports "why is php using 2 GB of RAM?" -
this is literally a daily question you get woth relation to Doctrine when
people try to run bulk operations, do not disable trace logger and them run
into the memory limit.What you are proposing is a footgun at it's finest and PHP has a rich
history of those and we have learned from the experience as a community.
Things like this are left to the userland. There are many libraries that
help handle this.On Tue, Feb 6, 2024, 17:35 Григорий Senior PHP / Разработчик Web <
6562680@gmail.com> wrote:Javascript is closer to.
It allows you to throw anything, but it is still the throw statement,
keeping in the mind the async nature of js - memory and processor stuff
is
shared by the time.JS seniors usually hate those guys who throw anything except language
Error
class because they skipped the mandatory level of programming - OOP. They
are now taking fun from it. We're tired of OOP for now. Once you work
with
batches/queue/bulks you need pipelines and chaining, and there's a throw
works only to stop any certain tasks and almost immediately catch the
next
line. Sothrow
is required to be safe-shield, but solves not enough
count
of cases.Old, maybe 10 years ago, Fowler's article about "errors is not an
exception". He explained why, but recommend to implement own error bag. I
tried few times implement own error bag on production ready code. And
this
is the hell of rewriting full nesting tree and carrying that return
statement to upper level again and again, then you start to get confused,
then you rewrite all return to objects with properties/getters/setters,
then you understand your PHPStorm started to lag because of 70 uses in
class... Better to use global error bag stack that you can enable or
disable for your needs outside function ("in controller", GRASP) or
inside
function directly like old good times $errors[] and if ($errors) { return
null; }вт, 6 февр. 2024 г. в 18:23, Alex Wells autaut03@gmail.com:
On Tue, Feb 6, 2024 at 3:58 PM Григорий Senior PHP / Разработчик Web <
6562680@gmail.com> wrote:
- add non-breakable interface and language construct
raise
to
"throw"
error without collecting trace- that error could be any scalar or object, or you can implement new
interface for them, keeping that is nested and taggable array- this
raise
could be catched same way as \Throwable allowing log it
anywhere you need or re-raise
againraise
statement won't start to collapse/break code, so it could be
skipped without affecting applicationIs there an existing language that does that, having both exceptions
and
these silent raise statements?--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ
связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com
--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com
Btw, i agree about Javascript, but on a low level it produces the most
clean code, because there's no types and rules. All types moved to
TypeScript's client side compiler.JS 15 years ago ACCIDENTALLY created a pipeline. Named it "Promise". We
spent years after to understand that while (true) and then/catch should be
different patterns.
I assume much of this thread is a language-barrier issue, which is making it more hostile than it needs to be. So let me try and expand a bit, because I am actually quite sympathetic to the OP's request, though not the way it's being made.
First of all, please don't top post. It is considered rude on this list. GMail makes it a bit annoying to bottom post but it can be done. Please do so.
Second, there's considerable prior art and discussion on the topic of error handling and exceptions. In particular, I recommend this excellent article by Joe Duffy:
https://joeduffyblog.com/2016/02/07/the-error-model/
And this one from me:
https://peakd.com/hive-168588/@crell/much-ado-about-null
Yes, they're long, but error handling is a topic that requires more than casual thought.
To summarize the articles for the short of time:
- Exceptions in many languages (including PHP) are very expensive. The stack trace is one of the most expensive things PHP does. This is one of the reasons why exceptions are terrible for flow control.
- Unchecked exceptions (where a function doesn't define what it can throw) are a great way to break your application in exciting and unpredictable ways. (This is the other reason exceptions are terrible for flow control.)
- Many people find checked exceptions cumbersome, even though they are better (for reasons Duffy goes into). This is due mostly to bad design of checked exceptions in JVM languages.
- The real problem is that we have a channel for a success case (return value), a channel for catastrophic failure (exceptions), but no channel for mundane errors (things a responsible developer should expect and know how to handle gracefully). So those get shoved into one or the other, usually an exception.
The need is for a "mundane error" channel. I agree with this. Different languages handle it in different ways.
- Go has multi-returns.
- Rust has the Result type (which is an Either monad), and an auto-propagation operator (?).
- PHP has union types (though that's not a deliberate design, just an emergent one).
One of the key differences between different approaches is whether they force you to handle error cases (Result type) or let you ignore errors and assume a happy path (Go, PHP), letting an unhappy path just explode later on. Whether the language should force you to think about unhappy paths is a complex and subjective question that I won't delve into now beyond saying that there's valid arguments and use cases for both designs.
As noted in the article above, I've started using enums and union type returns a lot for error handling and it's pretty nice, all things considered. That works today in all supported PHP verisons (8.1+). That said, it's not perfect, in part because it's not standardized and there's no really good language-level automation around it.
If we had generics and ADTs, building a Rust-like Result type would be super easy, and I'd suggest we include one in the stdlib for consistency. We'll probably get ADTs eventually, but generics are not something I'd bank on, so that is out.
HOWEVER, and this is where the important part lies, an open, lightweight, checked exception system is isomorphic to a Result object. It's just unwrapped. Compare these hypotheticals:
class DivByZero {}
(this could also be an enum case, but being generic for now.)
function divide(float $a, float $b): Result<int, DivByZero>
{
if ($b === 0) return new Result::Err(new DivByZero());
return new Result::OK($a/$b);
}
$result = divide(5, 0);
$x = match ($result) {
is Result::OK($x) => $x,
is Result::Err => // Do some kind of error handling.
}
vs.
function divide(float $a, float $b): int raises DivByZero
{
if ($b === 0) raise new DivByZero();
return new $a/$b;
}
try {
$result = divide(5, 0);
// Do stuff with $result, knowing it is valid.
} catch (DivByZero) {
// Do some kind of error handling.
}
These two samples are logically identical, and even have mostly the same performance characteristics, and both expose useful data to static analyzers. They're just spelled differently. The advantage of the second is that it could be implemented without generics. (ADTs would be an optional nice-to-have.) And if the caller doesn't handle DivByZero, it would try to pass it up to its caller, but being checked it would require the caller to also declare that it can raise DivByZero.
The second option could also be improved by other syntactic sugar to make it easier to work with, like Rust has. For example:
try $result = divide(5, 0) catch (DivByZero) { // Error handling that evaluates to a value }
Or by making null-aware operators (??, ?->, etc.) treat raised light-exceptions as if they were null to make pipelining easier. Or various other ideas I'm just giving examples of for the moment to make the point, but let's not get off on that tangent. (I would also note that I agree entirely such a system should only support objects, not primitives.)
This would provide a far better alternative to the "returns value on success or false on failure" anti-pattern throughout the stdlib, and to the common "returns value on success or null on failure or value not found or any other possible issue" pattern common in user-space code. (No, I don't expect us to go back and change all of stdlib; just pointing out that it's a known language limitation, and this would be the solution.)
To be clear: I really like this concept and have discussed it with others before, using almost exactly this syntax. I have not proposed it because my read of Internals lately is that there's no stomach for more type-centric behavior, especially with the obvious "But we already have exceptions, what's yer problem?" response (which is valid to a point, but also incomplete for reasons explained above). The responses in this thread so far confirm that fear, but as an optimist I'd be very happy to be proven wrong if there is an appetite for improving error handling via the type system.
Absent that, union types and enums (or really any interfaced object) or a purpose-built Either object are the best options today, and while they're not ideal, they're not bad options either.
None of that logic or argument requires sh*tting on OOP as a concept or abusing others on the list, however. Doing that only undermines the valid point that there is ample headroom to improve PHP's error handling.
--Larry Garfield
On Tue, Feb 6, 2024, at 4:13 PM, Григорий Senior PHP / Разработчик Web
wrote:Btw, i agree about Javascript, but on a low level it produces the most
clean code, because there's no types and rules. All types moved to
TypeScript's client side compiler.JS 15 years ago ACCIDENTALLY created a pipeline. Named it "Promise". We
spent years after to understand that while (true) and then/catch should
be
different patterns.I assume much of this thread is a language-barrier issue, which is making
it more hostile than it needs to be. So let me try and expand a bit,
because I am actually quite sympathetic to the OP's request, though not the
way it's being made.First of all, please don't top post. It is considered rude on this list.
GMail makes it a bit annoying to bottom post but it can be done. Please do
so.Second, there's considerable prior art and discussion on the topic of
error handling and exceptions. In particular, I recommend this excellent
article by Joe Duffy:https://joeduffyblog.com/2016/02/07/the-error-model/
And this one from me:
https://peakd.com/hive-168588/@crell/much-ado-about-null
Yes, they're long, but error handling is a topic that requires more than
casual thought.To summarize the articles for the short of time:
- Exceptions in many languages (including PHP) are very expensive. The
stack trace is one of the most expensive things PHP does. This is one of
the reasons why exceptions are terrible for flow control.- Unchecked exceptions (where a function doesn't define what it can throw)
are a great way to break your application in exciting and unpredictable
ways. (This is the other reason exceptions are terrible for flow control.)- Many people find checked exceptions cumbersome, even though they are
better (for reasons Duffy goes into). This is due mostly to bad design of
checked exceptions in JVM languages.- The real problem is that we have a channel for a success case (return
value), a channel for catastrophic failure (exceptions), but no channel for
mundane errors (things a responsible developer should expect and know how
to handle gracefully). So those get shoved into one or the other, usually
an exception.The need is for a "mundane error" channel. I agree with this. Different
languages handle it in different ways.
- Go has multi-returns.
- Rust has the Result type (which is an Either monad), and an
auto-propagation operator (?).- PHP has union types (though that's not a deliberate design, just an
emergent one).One of the key differences between different approaches is whether they
force you to handle error cases (Result type) or let you ignore errors and
assume a happy path (Go, PHP), letting an unhappy path just explode later
on. Whether the language should force you to think about unhappy paths is
a complex and subjective question that I won't delve into now beyond saying
that there's valid arguments and use cases for both designs.As noted in the article above, I've started using enums and union type
returns a lot for error handling and it's pretty nice, all things
considered. That works today in all supported PHP verisons (8.1+). That
said, it's not perfect, in part because it's not standardized and there's
no really good language-level automation around it.If we had generics and ADTs, building a Rust-like Result type would be
super easy, and I'd suggest we include one in the stdlib for consistency.
We'll probably get ADTs eventually, but generics are not something I'd bank
on, so that is out.HOWEVER, and this is where the important part lies, an open, lightweight,
checked exception system is isomorphic to a Result object. It's just
unwrapped. Compare these hypotheticals:class DivByZero {}
(this could also be an enum case, but being generic for now.)function divide(float $a, float $b): Result<int, DivByZero>
{
if ($b === 0) return new Result::Err(new DivByZero());
return new Result::OK($a/$b);
}$result = divide(5, 0);
$x = match ($result) {
is Result::OK($x) => $x,
is Result::Err => // Do some kind of error handling.
}vs.
function divide(float $a, float $b): int raises DivByZero
{
if ($b === 0) raise new DivByZero();
return new $a/$b;
}try {
$result = divide(5, 0);
// Do stuff with $result, knowing it is valid.
} catch (DivByZero) {
// Do some kind of error handling.
}These two samples are logically identical, and even have mostly the same
performance characteristics, and both expose useful data to static
analyzers. They're just spelled differently. The advantage of the second
is that it could be implemented without generics. (ADTs would be an
optional nice-to-have.) And if the caller doesn't handle DivByZero, it
would try to pass it up to its caller, but being checked it would require
the caller to also declare that it can raise DivByZero.The second option could also be improved by other syntactic sugar to make
it easier to work with, like Rust has. For example:try $result = divide(5, 0) catch (DivByZero) { // Error handling that
evaluates to a value }Or by making null-aware operators (??, ?->, etc.) treat raised
light-exceptions as if they were null to make pipelining easier. Or
various other ideas I'm just giving examples of for the moment to make the
point, but let's not get off on that tangent. (I would also note that I
agree entirely such a system should only support objects, not primitives.)This would provide a far better alternative to the "returns value on
success or false on failure" anti-pattern throughout the stdlib, and to the
common "returns value on success or null on failure or value not found or
any other possible issue" pattern common in user-space code. (No, I don't
expect us to go back and change all of stdlib; just pointing out that it's
a known language limitation, and this would be the solution.)To be clear: I really like this concept and have discussed it with others
before, using almost exactly this syntax. I have not proposed it because
my read of Internals lately is that there's no stomach for more
type-centric behavior, especially with the obvious "But we already have
exceptions, what's yer problem?" response (which is valid to a point, but
also incomplete for reasons explained above). The responses in this thread
so far confirm that fear, but as an optimist I'd be very happy to be proven
wrong if there is an appetite for improving error handling via the type
system.Absent that, union types and enums (or really any interfaced object) or a
purpose-built Either object are the best options today, and while they're
not ideal, they're not bad options either.None of that logic or argument requires sh*tting on OOP as a concept or
abusing others on the list, however. Doing that only undermines the valid
point that there is ample headroom to improve PHP's error handling.--Larry Garfield
--
To unsubscribe, visit: https://www.php.net/unsub.php
Thank you Larry for this interesting summary - didn't remember there was
quite a bit a discussion around the topic prior.
I lean on the "we have exceptions, just leave it be" side out of practical
reasons - the vast majority of OO code has standardized around the approach
and interoperability is high. It makes using code that's out there super
easy and predictable - almost nobody uses the "return false|0|-1" out there
(at least I haven't used code like that except the PHP's stdlib, and even
that has been changing little by little). It makes error handling
predictable, and considering the type of code we mostly write in PHP - most
of the time we leave the catching to the global top-level handler or
sentry/bugsnag/etc libraries.
Consistency is the word I want to highlight here. For better or for worse -
it's the method the PHP ecosystem arrived at and it's the predominant one.
Introducing a distinctly different method of error handling is going to
bring in wrappers around libraries that convert errors to one style or the
other, the application code can end up using different ways of error
handling, etc, etc. My approach is to grab a different language aka "the
right tool for the job" if I want to build things differently, that's why
we have so many programming languages and not just a few :)
I'd put resources into optimising the VM and php engine to handle the
exceptions better and if there are improvements to be had there - do those
maybe? (I suspect JIT is also going to influence this a lot going forward).
Sincerely,
Arvīds Godjuks
+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihius
On Tue, Feb 6, 2024 at 11:19 AM Arvids Godjuks arvids.godjuks@gmail.com
wrote:
On Tue, 6 Feb 2024 at 19:14, Larry Garfield larry@garfieldtech.com
wrote:Thank you Larry for this interesting summary - didn't remember there was
quite a bit a discussion around the topic prior.I lean on the "we have exceptions, just leave it be" side out of practical
reasons - the vast majority of OO code has standardized around the approach
and interoperability is high. It makes using code that's out there super
easy and predictable - almost nobody uses the "return false|0|-1" out there
(at least I haven't used code like that except the PHP's stdlib, and even
that has been changing little by little). It makes error handling
predictable, and considering the type of code we mostly write in PHP - most
of the time we leave the catching to the global top-level handler or
sentry/bugsnag/etc libraries.
Consistency is the word I want to highlight here. For better or for worse -
it's the method the PHP ecosystem arrived at and it's the predominant one.
Introducing a distinctly different method of error handling is going to
bring in wrappers around libraries that convert errors to one style or the
other, the application code can end up using different ways of error
handling, etc, etc. My approach is to grab a different language aka "the
right tool for the job" if I want to build things differently, that's why
we have so many programming languages and not just a few :)I'd put resources into optimising the VM and php engine to handle the
exceptions better and if there are improvements to be had there - do those
maybe? (I suspect JIT is also going to influence this a lot going forward).Sincerely,
Arvīds Godjuks
When what you have is a situation where a function or block of code goes "I
know something fixable went wrong, but only the block above me in the
execution stack knows what to do about it", Exceptions are extremely
overkill. But they are the only "sane" option in PHP in a lot of situations.
PHP simply doesn't have a language level structure to handle this VERY
COMMON situation. The fact that people have standardized on Exceptions for
this is not a point in favor of Exceptions. It is a sign of how much extra
performance and semantic correctness we COULD provide to the language by
improving this area of error handling.
I don't know if the OP of this email thread was referring to this
situation. It was honestly very difficult for me to follow what they were
even asking for given the language barrier. But I am 1000% behind the
problem space that Larry is describing. Exceptions are not a solution to
that problem, they are duct tape.
Jordan
Thanks Larry, I will read both articles next weekend.
Am not even talking about changing throw
to raise
.
Am talking only about:
- production ready code
- that should be able to refactor with error collectors (that was not
implemented years ago) - without touching return types
- without touching input arguments of existing code
- without possible code fall after throw exception: you have to try/catch
all places you use that function (sometimes you predict possible error, and
yes, write return class/enum to extend/refactor it later)
(and yes, if old code did not support returning null/null-object before -
you have to refactor return types then)
While working with queues you have a list of tasks
- then you reduce it to smaller with reducer (unique/filter/merge)
- then do some queries
- then walk initial data using reduced results: copying reports to save
errors/warnings to each task separately
It cannot be solved with exceptions. In addition, large arrays throw
exceptions that cause timeloss. It's definitely not a tool for.
Also your method could return many errors (today - only one
error/exception), and you need to write a second method, then call the
second method, then debug the second method.
So what's in rest? Arrays collection of warnings and errors. Changing
return types or passing second-return by reference.
[ Enum case ~ DTO output ] covers newly written code. Old code is
uncovered. You have to rewrite a full tree, that's why some trick is
necessary.
I did it my way with an error bag stack. I enable it inside the function or
in place I call the function. I want to share this experience, and imagined
it would be better for all users. It could be done without 2 classes, 10
functions and work with push/pop/current (closer to ob_start/ob_get_clean
story).
I guess it could be implemented if raise
world will put any data to the
current error bag in the stack. Exactly if the current error bag is present
(declared manually like you can declare() strict types or ticks for some
scope).
I agree that there's more mandatory problems to solve that I didn't even
know about.
I tried to talk about error handling with a few developers, all of them
recommend:
- Use exceptions, don't make anything fresh
- Do validation at the script start to reduce the count of errors later
I've just encountered cases where bugs come from within - once you
integrate a really bad external system with its own checks, which are
described in hundreds of documents, I'm sure you'll encounter new bugs once
the "working" code is released to production. And then you will need to
quickly and easily reorganize it.
And you can't.
And you will be sad.
And, "PHP moves differently" is a completely wrong principle, I believe in
"watching for".
--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com
Thanks Larry, I will read both articles next weekend.
Am not even talking about changing
throw
toraise
.Am talking only about:
- production ready code
- that should be able to refactor with error collectors (that was not
implemented years ago)- without touching return types
- without touching input arguments of existing code
- without possible code fall after throw exception: you have to try/catch
all places you use that function (sometimes you predict possible error, and
yes, write return class/enum to extend/refactor it later)
(and yes, if old code did not support returning null/null-object before -
you have to refactor return types then)While working with queues you have a list of tasks
- then you reduce it to smaller with reducer (unique/filter/merge)
- then do some queries
- then walk initial data using reduced results: copying reports to save
errors/warnings to each task separatelyIt cannot be solved with exceptions. In addition, large arrays throw
exceptions that cause timeloss. It's definitely not a tool for.
Also your method could return many errors (today - only one
error/exception), and you need to write a second method, then call the
second method, then debug the second method.So what's in rest? Arrays collection of warnings and errors. Changing
return types or passing second-return by reference.[ Enum case ~ DTO output ] covers newly written code. Old code is
uncovered. You have to rewrite a full tree, that's why some trick is
necessary.I did it my way with an error bag stack. I enable it inside the function or
in place I call the function. I want to share this experience, and imagined
it would be better for all users. It could be done without 2 classes, 10
functions and work with push/pop/current (closer to ob_start/ob_get_clean
story).
I guess it could be implemented ifraise
world will put any data to the
current error bag in the stack. Exactly if the current error bag is present
(declared manually like you can declare() strict types or ticks for some
scope).I agree that there's more mandatory problems to solve that I didn't even
know about.
I tried to talk about error handling with a few developers, all of them
recommend:
- Use exceptions, don't make anything fresh
- Do validation at the script start to reduce the count of errors later
I've just encountered cases where bugs come from within - once you
integrate a really bad external system with its own checks, which are
described in hundreds of documents, I'm sure you'll encounter new bugs once
the "working" code is released to production. And then you will need to
quickly and easily reorganize it.And you can't.
And you will be sad.
And, "PHP moves differently" is a completely wrong principle, I believe in
"watching for".
I think there's a subtle but important difference here between what you're describing as the problem and what you implied the solution was (which I then ran with).
What you're talking about is trying to change the error handling model of existing code without changing function signatures. There are only two possible ways to do that, both of them bad: Unchecked exceptions and globals.
What I described, based on the syntax you offered, is checked exceptions, which necessarily means changing the function signature. Error handling is part of the contract of a function. If its error handling changes, it should have a signature change to indicate that. (That unchecked exceptions do not do that is the problem with unchecked exceptions.) So if "no changes to existing code" is the goal, checked exceptions as I describe them are not the answer you are looking for.
It seems from your latest message that you're describing more a generalized version of json_last_error()
and similar functions. The problem there is that such an API design is generally considered very poor practice outside of C, because it's all necessarily based on globals and "hope you remembered to check the thing that no one told you to check and is not even slightly obvious to check". That is not something I would want better support for in the language at all. There's probably cleaner ways to emulate it in user-space, but that is for a particular application to sort out. There's definitely cleaner monadic solutions (which I've written before and are quite neat) using a writer/logger monad, but that again doesn't meet your "don't change existing code" requirement. I don't think anything the language can do will meet that requirement and be a good design.
--Larry Garfield
Thanks Larry, I will read both articles next weekend.
Am not even talking about changing
throw
toraise
.Am talking only about:
- production ready code
- that should be able to refactor with error collectors (that was not
implemented years ago)- without touching return types
- without touching input arguments of existing code
- without possible code fall after throw exception: you have to try/catch
all places you use that function (sometimes you predict possible error, and
yes, write return class/enum to extend/refactor it later)
(and yes, if old code did not support returning null/null-object before -
you have to refactor return types then)While working with queues you have a list of tasks
- then you reduce it to smaller with reducer (unique/filter/merge)
- then do some queries
- then walk initial data using reduced results: copying reports to save
errors/warnings to each task separatelyIt cannot be solved with exceptions. In addition, large arrays throw
exceptions that cause timeloss. It's definitely not a tool for.
Also your method could return many errors (today - only one
error/exception), and you need to write a second method, then call the
second method, then debug the second method.So what's in rest? Arrays collection of warnings and errors. Changing
return types or passing second-return by reference.[ Enum case ~ DTO output ] covers newly written code. Old code is
uncovered. You have to rewrite a full tree, that's why some trick is
necessary.I did it my way with an error bag stack. I enable it inside the function or
in place I call the function. I want to share this experience, and imagined
it would be better for all users. It could be done without 2 classes, 10
functions and work with push/pop/current (closer to ob_start/ob_get_clean
story).
I guess it could be implemented ifraise
world will put any data to the
current error bag in the stack. Exactly if the current error bag is present
(declared manually like you can declare() strict types or ticks for some
scope).I agree that there's more mandatory problems to solve that I didn't even
know about.
I tried to talk about error handling with a few developers, all of them
recommend:
- Use exceptions, don't make anything fresh
- Do validation at the script start to reduce the count of errors later
I've just encountered cases where bugs come from within - once you
integrate a really bad external system with its own checks, which are
described in hundreds of documents, I'm sure you'll encounter new bugs once
the "working" code is released to production. And then you will need to
quickly and easily reorganize it.And you can't.
And you will be sad.
And, "PHP moves differently" is a completely wrong principle, I believe in
"watching for".
I think there's a subtle but important difference here between what you're describing as the problem and what you implied the solution was (which I then ran with).What you're talking about is trying to change the error handling model of existing code without changing function signatures. There are only two possible ways to do that, both of them bad: Unchecked exceptions and globals.
What I described, based on the syntax you offered, is checked exceptions, which necessarily means changing the function signature. Error handling is part of the contract of a function. If its error handling changes, it should have a signature change to indicate that. (That unchecked exceptions do not do that is the problem with unchecked exceptions.) So if "no changes to existing code" is the goal, checked exceptions as I describe them are not the answer you are looking for.
It seems from your latest message that you're describing more a generalized version of
json_last_error()
and similar functions. The problem there is that such an API design is generally considered very poor practice outside of C, because it's all necessarily based on globals and "hope you remembered to check the thing that no one told you to check and is not even slightly obvious to check". That is not something I would want better support for in the language at all. There's probably cleaner ways to emulate it in user-space, but that is for a particular application to sort out. There's definitely cleaner monadic solutions (which I've written before and are quite neat) using a writer/logger monad, but that again doesn't meet your "don't change existing code" requirement. I don't think anything the language can do will meet that requirement and be a good design.--Larry Garfield
--
To unsubscribe, visit: https://www.php.net/unsub.php
Oh wow, this conversation got really interesting while I was asleep :D
I think this could actually be interesting in a
semi-backwards-compatible way, by just adding some syntax sugar:
function getResult(): ?Result, ?ResultError {
if($error) return null, $error;
}
instead of, but this would still work when destructuring:
function getResult(): array {
if($error) return [null, $error);
}
This would still work (the "backwards compatible" part):
[$result, $error] = getResult();
or this:
$result, $error = getResult();
Essentially, return types with a comma are just a "strongly typed
array" and a comma on the left-hand side of assignment is just a
destructure.
Robert Landers
Software Engineer
Utrecht NL
Yes, that was the second build of the error bag I presented in the github
ticket.
First one was a Trait that added an error bag as property in class and
returned an array with two results.
The trouble was you have to refactor all places you used your function
doesn't matter, want you or not.
And the last build just collects errors to the global stack of error bags
allowing you to stay untouched by the existing code.
Of course, if code should start to return null/null-object as result - you
have to implement that (refactoring the places where null is inapplicable),
but error collection itself won't change existing code then, it works like
an observer pattern now.
ср, 7 февр. 2024 г. в 13:21, Robert Landers landers.robert@gmail.com:
On Tue, Feb 6, 2024 at 9:22 PM Larry Garfield larry@garfieldtech.com
wrote:On Tue, Feb 6, 2024, at 7:56 PM, Григорий Senior PHP / Разработчик Web
wrote:Thanks Larry, I will read both articles next weekend.
Am not even talking about changing
throw
toraise
.Am talking only about:
- production ready code
- that should be able to refactor with error collectors (that was not
implemented years ago)- without touching return types
- without touching input arguments of existing code
- without possible code fall after throw exception: you have to
try/catch
all places you use that function (sometimes you predict possible
error, and
yes, write return class/enum to extend/refactor it later)
(and yes, if old code did not support returning null/null-object
before -
you have to refactor return types then)While working with queues you have a list of tasks
- then you reduce it to smaller with reducer (unique/filter/merge)
- then do some queries
- then walk initial data using reduced results: copying reports to save
errors/warnings to each task separatelyIt cannot be solved with exceptions. In addition, large arrays throw
exceptions that cause timeloss. It's definitely not a tool for.
Also your method could return many errors (today - only one
error/exception), and you need to write a second method, then call the
second method, then debug the second method.So what's in rest? Arrays collection of warnings and errors. Changing
return types or passing second-return by reference.[ Enum case ~ DTO output ] covers newly written code. Old code is
uncovered. You have to rewrite a full tree, that's why some trick is
necessary.I did it my way with an error bag stack. I enable it inside the
function or
in place I call the function. I want to share this experience, and
imagined
it would be better for all users. It could be done without 2 classes,
10
functions and work with push/pop/current (closer to
ob_start/ob_get_clean
story).
I guess it could be implemented ifraise
world will put any data to
the
current error bag in the stack. Exactly if the current error bag is
present
(declared manually like you can declare() strict types or ticks for
some
scope).I agree that there's more mandatory problems to solve that I didn't
even
know about.
I tried to talk about error handling with a few developers, all of them
recommend:
- Use exceptions, don't make anything fresh
- Do validation at the script start to reduce the count of errors
laterI've just encountered cases where bugs come from within - once you
integrate a really bad external system with its own checks, which are
described in hundreds of documents, I'm sure you'll encounter new bugs
once
the "working" code is released to production. And then you will need to
quickly and easily reorganize it.And you can't.
And you will be sad.
And, "PHP moves differently" is a completely wrong principle, I
believe in
"watching for".
I think there's a subtle but important difference here between what
you're describing as the problem and what you implied the solution was
(which I then ran with).What you're talking about is trying to change the error handling model
of existing code without changing function signatures. There are only two
possible ways to do that, both of them bad: Unchecked exceptions and
globals.What I described, based on the syntax you offered, is checked
exceptions, which necessarily means changing the function signature. Error
handling is part of the contract of a function. If its error handling
changes, it should have a signature change to indicate that. (That
unchecked exceptions do not do that is the problem with unchecked
exceptions.) So if "no changes to existing code" is the goal, checked
exceptions as I describe them are not the answer you are looking for.It seems from your latest message that you're describing more a
generalized version ofjson_last_error()
and similar functions. The
problem there is that such an API design is generally considered very poor
practice outside of C, because it's all necessarily based on globals and
"hope you remembered to check the thing that no one told you to check and
is not even slightly obvious to check". That is not something I would want
better support for in the language at all. There's probably cleaner ways
to emulate it in user-space, but that is for a particular application to
sort out. There's definitely cleaner monadic solutions (which I've written
before and are quite neat) using a writer/logger monad, but that again
doesn't meet your "don't change existing code" requirement. I don't think
anything the language can do will meet that requirement and be a good
design.--Larry Garfield
--
To unsubscribe, visit: https://www.php.net/unsub.php
Oh wow, this conversation got really interesting while I was asleep :D
I think this could actually be interesting in a
semi-backwards-compatible way, by just adding some syntax sugar:function getResult(): ?Result, ?ResultError {
if($error) return null, $error;
}instead of, but this would still work when destructuring:
function getResult(): array {
if($error) return [null, $error);
}This would still work (the "backwards compatible" part):
[$result, $error] = getResult();
or this:
$result, $error = getResult();
Essentially, return types with a comma are just a "strongly typed
array" and a comma on the left-hand side of assignment is just a
destructure.Robert Landers
Software Engineer
Utrecht NL--
To unsubscribe, visit: https://www.php.net/unsub.php
--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com
On Wed, Feb 7, 2024 at 11:27 AM Григорий Senior PHP / Разработчик Web
6562680@gmail.com wrote:
Yes, that was the second build of the error bag I presented in the github ticket.
First one was a Trait that added an error bag as property in class and returned an array with two results.The trouble was you have to refactor all places you used your function doesn't matter, want you or not.
And the last build just collects errors to the global stack of error bags allowing you to stay untouched by the existing code.
Of course, if code should start to return null/null-object as result - you have to implement that (refactoring the places where null is inapplicable), but error collection itself won't change existing code then, it works like an observer pattern now.
ср, 7 февр. 2024 г. в 13:21, Robert Landers landers.robert@gmail.com:
Thanks Larry, I will read both articles next weekend.
Am not even talking about changing
throw
toraise
.Am talking only about:
- production ready code
- that should be able to refactor with error collectors (that was not
implemented years ago)- without touching return types
- without touching input arguments of existing code
- without possible code fall after throw exception: you have to try/catch
all places you use that function (sometimes you predict possible error, and
yes, write return class/enum to extend/refactor it later)
(and yes, if old code did not support returning null/null-object before -
you have to refactor return types then)While working with queues you have a list of tasks
- then you reduce it to smaller with reducer (unique/filter/merge)
- then do some queries
- then walk initial data using reduced results: copying reports to save
errors/warnings to each task separatelyIt cannot be solved with exceptions. In addition, large arrays throw
exceptions that cause timeloss. It's definitely not a tool for.
Also your method could return many errors (today - only one
error/exception), and you need to write a second method, then call the
second method, then debug the second method.So what's in rest? Arrays collection of warnings and errors. Changing
return types or passing second-return by reference.[ Enum case ~ DTO output ] covers newly written code. Old code is
uncovered. You have to rewrite a full tree, that's why some trick is
necessary.I did it my way with an error bag stack. I enable it inside the function or
in place I call the function. I want to share this experience, and imagined
it would be better for all users. It could be done without 2 classes, 10
functions and work with push/pop/current (closer to ob_start/ob_get_clean
story).
I guess it could be implemented ifraise
world will put any data to the
current error bag in the stack. Exactly if the current error bag is present
(declared manually like you can declare() strict types or ticks for some
scope).I agree that there's more mandatory problems to solve that I didn't even
know about.
I tried to talk about error handling with a few developers, all of them
recommend:
- Use exceptions, don't make anything fresh
- Do validation at the script start to reduce the count of errors later
I've just encountered cases where bugs come from within - once you
integrate a really bad external system with its own checks, which are
described in hundreds of documents, I'm sure you'll encounter new bugs once
the "working" code is released to production. And then you will need to
quickly and easily reorganize it.And you can't.
And you will be sad.
And, "PHP moves differently" is a completely wrong principle, I believe in
"watching for".
I think there's a subtle but important difference here between what you're describing as the problem and what you implied the solution was (which I then ran with).What you're talking about is trying to change the error handling model of existing code without changing function signatures. There are only two possible ways to do that, both of them bad: Unchecked exceptions and globals.
What I described, based on the syntax you offered, is checked exceptions, which necessarily means changing the function signature. Error handling is part of the contract of a function. If its error handling changes, it should have a signature change to indicate that. (That unchecked exceptions do not do that is the problem with unchecked exceptions.) So if "no changes to existing code" is the goal, checked exceptions as I describe them are not the answer you are looking for.
It seems from your latest message that you're describing more a generalized version of
json_last_error()
and similar functions. The problem there is that such an API design is generally considered very poor practice outside of C, because it's all necessarily based on globals and "hope you remembered to check the thing that no one told you to check and is not even slightly obvious to check". That is not something I would want better support for in the language at all. There's probably cleaner ways to emulate it in user-space, but that is for a particular application to sort out. There's definitely cleaner monadic solutions (which I've written before and are quite neat) using a writer/logger monad, but that again doesn't meet your "don't change existing code" requirement. I don't think anything the language can do will meet that requirement and be a good design.--Larry Garfield
--
To unsubscribe, visit: https://www.php.net/unsub.php
Oh wow, this conversation got really interesting while I was asleep :D
I think this could actually be interesting in a
semi-backwards-compatible way, by just adding some syntax sugar:function getResult(): ?Result, ?ResultError {
if($error) return null, $error;
}instead of, but this would still work when destructuring:
function getResult(): array {
if($error) return [null, $error);
}This would still work (the "backwards compatible" part):
[$result, $error] = getResult();
or this:
$result, $error = getResult();
Essentially, return types with a comma are just a "strongly typed
array" and a comma on the left-hand side of assignment is just a
destructure.Robert Landers
Software Engineer
Utrecht NL--
To unsubscribe, visit: https://www.php.net/unsub.php
--
+375 (29) 676-48-68 / Mobile - предпочитаемый способ связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com
Please don't top-post your replies. It's very rude.
If it's just syntax around arrays, it's something that doesn't require
much (if any refactoring)
$result = getResult();
could be seen by PHP as the same as
[$result] = getResult();
Allowing you to implement things as fast or as slow as you wanted to,
or ignore errors completely.
Robert Landers
Software Engineer
Utrecht NL
I think there's a subtle but important difference here between what you're describing as the problem and what you implied the solution was (which I then ran with).
What you're talking about is trying to change the error handling model of existing code without changing function signatures. There are only two possible ways to do that, both of them bad: Unchecked exceptions and globals.
What I described, based on the syntax you offered, is checked exceptions, which necessarily means changing the function signature. Error handling is part of the contract of a function. If its error handling changes, it should have a signature change to indicate that. (That unchecked exceptions do not do that is the problem with unchecked exceptions.) So if "no changes to existing code" is the goal, checked exceptions as I describe them are not the answer you are looking for.
It seems from your latest message that you're describing more a generalized version of
json_last_error()
and similar functions. The problem there is that such an API design is generally considered very poor practice outside of C, because it's all necessarily based on globals and "hope you remembered to check the thing that no one told you to check and is not even slightly obvious to check". That is not something I would want better support for in the language at all. There's probably cleaner ways to emulate it in user-space, but that is for a particular application to sort out. There's definitely cleaner monadic solutions (which I've written before and are quite neat) using a writer/logger monad, but that again doesn't meet your "don't change existing code" requirement. I don't think anything the language can do will meet that requirement and be a good design.--Larry Garfield
Oh wow, this conversation got really interesting while I was asleep :D
I think this could actually be interesting in a
semi-backwards-compatible way, by just adding some syntax sugar:function getResult(): ?Result, ?ResultError {
if($error) return null, $error;
}instead of, but this would still work when destructuring:
function getResult(): array {
if($error) return [null, $error);
}This would still work (the "backwards compatible" part):
[$result, $error] = getResult();
or this:
$result, $error = getResult();
Essentially, return types with a comma are just a "strongly typed
array" and a comma on the left-hand side of assignment is just a
destructure.
What you're describing here is basically porting Go multi-returns to PHP. That is also an option, though I would probably not be in favor of it. For one, it doesn't really offer much beyond the union returns we already have. For two, it seems like it would necessitate nullable returns for both parts, which is a hard-no from me on type safety. It's going to confuse static analyzers badly.
For three, the necessary idioms around Go's error handling are legendarily the butt of jokes.
a, err := step_one()
if (err) {
return err
}
b, err := step_two(a)
if (err) {
return err
}
c, err := step_two(a, b)
if (err) {
return err
}
And so on. It's checked exceptions without the ergonomics. :-)
--Larry Garfield
Analyzing the problem on a single level operation is incorrect. New code
and simple operations are fully covered with exception/try/catch flow.
The main point i tried to attract your attention is that if you working in
batches/queues/pipelines you still need errors collection, and if you made
error flow with exceptions - you have to extract validations to separate
classes/dto-s, made return types as objects, try/catching as fast as
possible, and then you will still met errors from remote systems, network
errors, and invalid response parsing that need to be logged as much close
to task registration place.
So correct analysis - providing an example with at least foreaches/yields
instead of simply calling native functions. In small functions IF
statements are overkill, and even try-catch could be skipped, because the
whole script will fall and that's correct behavior.
Case is in that the script should continue working while working with
chaining/nesting, and should be able to easily refactor any step to skip
one or more bulk-data rows of each step on any nesting level.
As I mentioned above - javascript pipelines (named Promise
and
Thenable
) are great examples when exceptions are a bad choice. Btw, not
"bad", but "choice to stop one step of the chain only".
If any step of chain/foreach throws an exception - it will break all code.
Should break one step. That's why you throw the immediate catch. And
continue to check via if-checking once the result of the pipeline is
received.
Catching is a good solution for the application core. If some exception
pops up at the entrypoint - you could transform the exception to the
output. All other cases should be covered without exceptions. If you use
exceptions on any occurred error as "truth way" you will meet risk and also
timeloss on any misformatted batches/user-inputs.
ср, 7 февр. 2024 г. в 18:06, Григорий Senior PHP / Разработчик Web <
6562680@gmail.com>:
Analyzing the problem on a single level operation is incorrect. New code
and simple operations are fully covered with exception/try/catch flow.The main point i tried to attract your attention is that if you working in
batches/queues/pipelines you still need errors collection, and if you made
error flow with exceptions - you have to extract validations to separate
classes/dto-s, made return types as objects, try/catching as fast as
possible, and then you will still met errors from remote systems, network
errors, and invalid response parsing that need to be logged as much close
to task registration place.So correct analysis - providing an example with at least foreaches/yields
instead of simply calling native functions. In small functions IF
statements are overkill, and even try-catch could be skipped, because the
whole script will fall and that's correct behavior.Case is in that the script should continue working while working with
chaining/nesting, and should be able to easily refactor any step to skip
one or more bulk-data rows of each step on any nesting level.
--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com
To be clear: I really like this concept and have discussed it with others
before, using almost exactly this syntax. I have not proposed it because
my read of Internals lately is that there's no stomach for more
type-centric behavior, especially with the obvious "But we already have
exceptions, what's yer problem?" response (which is valid to a point, but
also incomplete for reasons explained above). The responses in this thread
so far confirm that fear, but as an optimist I'd be very happy to be proven
wrong if there is an appetite for improving error handling via the type
system.Absent that, union types and enums (or really any interfaced object) or a
purpose-built Either object are the best options today, and while they're
not ideal, they're not bad options either.None of that logic or argument requires sh*tting on OOP as a concept or
abusing others on the list, however. Doing that only undermines the valid
point that there is ample headroom to improve PHP's error handling.--Larry Garfield
--
To unsubscribe, visit: https://www.php.net/unsub.php
Thank you Larry for this interesting summary - didn't remember there was
quite a bit a discussion around the topic prior.I lean on the "we have exceptions, just leave it be" side out of practical
reasons - the vast majority of OO code has standardized around the approach
and interoperability is high. It makes using code that's out there super
easy and predictable - almost nobody uses the "return false|0|-1" out there
(at least I haven't used code like that except the PHP's stdlib, and even
that has been changing little by little). It makes error handling
predictable, and considering the type of code we mostly write in PHP - most
of the time we leave the catching to the global top-level handler or
sentry/bugsnag/etc libraries.
Consistency is the word I want to highlight here. For better or for worse -
it's the method the PHP ecosystem arrived at and it's the predominant one.
Introducing a distinctly different method of error handling is going to
bring in wrappers around libraries that convert errors to one style or the
other, the application code can end up using different ways of error
handling, etc, etc. My approach is to grab a different language aka "the
right tool for the job" if I want to build things differently, that's why
we have so many programming languages and not just a few :)I'd put resources into optimising the VM and php engine to handle the
exceptions better and if there are improvements to be had there - do those
maybe? (I suspect JIT is also going to influence this a lot going forward).
"The right tool for the job" is indeed the strongest argument for lightweight exceptions. It's a tool we lack right now.
I'm thinking not of "DB went away" type issues (Exceptions are already fine there), but "requested product not found." Right now, the options we have are:
public function find($id): ?Product {}
public function find($id): Product {
// This is very expensive I don't think will ever not be.
// It will also bubble up to the top of the application and crash the whole process,
// Or still show up in weird, unexpected places.
throw new NotFound();
}
public function find($id): Product|RepoError {}
enum RepoError {
case NotFound;
}
The first is probably most common, but null (as I go into in the article) doesn't tell you anything and leads to mismatch errors.
Exceptions, I'd argue, are just plain wrong in this situation. (Which means, yes, all the frameworks that throw exceptions on route-not-found are doing it wrong.)
And the union-enum approach is a bit clunky as it has no native language support, and no solid conventions behind it. This is my preferred approach personally today, but I think we can do better. Even just having this available at all means that "well everyone just uses unchecked exceptions" isn't entirely true. (All three of the above can be found in the wild.)
--Larry Garfield
To be clear: I really like this concept and have discussed it with
others
before, using almost exactly this syntax. I have not proposed it
because
my read of Internals lately is that there's no stomach for more
type-centric behavior, especially with the obvious "But we already have
exceptions, what's yer problem?" response (which is valid to a point,
but
also incomplete for reasons explained above). The responses in this
thread
so far confirm that fear, but as an optimist I'd be very happy to be
proven
wrong if there is an appetite for improving error handling via the type
system.Absent that, union types and enums (or really any interfaced object) or
a
purpose-built Either object are the best options today, and while
they're
not ideal, they're not bad options either.None of that logic or argument requires sh*tting on OOP as a concept or
abusing others on the list, however. Doing that only undermines the
valid
point that there is ample headroom to improve PHP's error handling.--Larry Garfield
--
To unsubscribe, visit: https://www.php.net/unsub.php
Thank you Larry for this interesting summary - didn't remember there was
quite a bit a discussion around the topic prior.I lean on the "we have exceptions, just leave it be" side out of
practical
reasons - the vast majority of OO code has standardized around the
approach
and interoperability is high. It makes using code that's out there super
easy and predictable - almost nobody uses the "return false|0|-1" out
there
(at least I haven't used code like that except the PHP's stdlib, and even
that has been changing little by little). It makes error handling
predictable, and considering the type of code we mostly write in PHP -
most
of the time we leave the catching to the global top-level handler or
sentry/bugsnag/etc libraries.
Consistency is the word I want to highlight here. For better or for
worse -
it's the method the PHP ecosystem arrived at and it's the predominant
one.
Introducing a distinctly different method of error handling is going to
bring in wrappers around libraries that convert errors to one style or
the
other, the application code can end up using different ways of error
handling, etc, etc. My approach is to grab a different language aka "the
right tool for the job" if I want to build things differently, that's why
we have so many programming languages and not just a few :)I'd put resources into optimising the VM and php engine to handle the
exceptions better and if there are improvements to be had there - do
those
maybe? (I suspect JIT is also going to influence this a lot going
forward)."The right tool for the job" is indeed the strongest argument for
lightweight exceptions. It's a tool we lack right now.I'm thinking not of "DB went away" type issues (Exceptions are already
fine there), but "requested product not found." Right now, the options we
have are:public function find($id): ?Product {}
public function find($id): Product {
// This is very expensive I don't think will ever not be.
// It will also bubble up to the top of the application and crash the
whole process,
// Or still show up in weird, unexpected places.
throw new NotFound();
}public function find($id): Product|RepoError {}
enum RepoError {
case NotFound;
}The first is probably most common, but null (as I go into in the article)
doesn't tell you anything and leads to mismatch errors.Exceptions, I'd argue, are just plain wrong in this situation. (Which
means, yes, all the frameworks that throw exceptions on route-not-found are
doing it wrong.)And the union-enum approach is a bit clunky as it has no native language
support, and no solid conventions behind it. This is my preferred approach
personally today, but I think we can do better. Even just having this
available at all means that "well everyone just uses unchecked exceptions"
isn't entirely true. (All three of the above can be found in the wild.)--Larry Garfield
And that, folks, is how you change people's minds.
I'm on board, Larry. I agree that things like route not found, entity not
found and so on don't have to be full-fat exceptions - in those cases, you
indeed don't need the stack trace and other parts of it. Even the error
message might not be required since whatever you "throw" or "raise" in this
case is self-explanatory just by its type/object/enum type.
--
Arvīds Godjuks
+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihius
"The right tool for the job" is indeed the strongest argument for lightweight exceptions. It's a tool we lack right now.
I'm thinking not of "DB went away" type issues (Exceptions are already fine there), but "requested product not found." Right now, the options we have are:
...
The first is probably most common, but null (as I go into in the article) doesn't tell you anything and leads to mismatch errors.
Exceptions, I'd argue, are just plain wrong in this situation. (Which means, yes, all the frameworks that throw exceptions on route-not-found are doing it wrong.)
And the union-enum approach is a bit clunky as it has no native language support, and no solid conventions behind it. This is my preferred approach personally today, but I think we can do better. Even just having this available at all means that "well everyone just uses unchecked exceptions" isn't entirely true. (All three of the above can be found in the wild.)
I can add a fourth option for the "get record by ID" that may not be
found, that I've seen in the wild:
public function find($id): ProductSet {}
Where the ProductSet is a collection of (in this case) no more than one
Product. It's up to the caller to verify there actually is a Product
inside and extract it, and it's also the caller's decision whether not
finding one is a problem or not.
Of course, in this domain it folded in with using ProductSets to
represent more general collections of Products and the resulting set
algebra.
Weedpacket
On Tue, Feb 6, 2024 at 7:14 PM Larry Garfield larry@garfieldtech.com
wrote:
These two samples are logically identical, and even have mostly the same
performance characteristics, and both expose useful data to static
analyzers. They're just spelled differently. The advantage of the second
is that it could be implemented without generics. (ADTs would be an
optional nice-to-have.) And if the caller doesn't handle DivByZero, it
would try to pass it up to its caller, but being checked it would require
the caller to also declare that it can raise DivByZero.
Let's assume that the developer knows the divisor isn't 0 - through an
assertion or an if
clause above the call to divide(5, $divisor)
. In
this case, DivByZero error cannot ever be thrown (or risen), but the
developer would still have to either handle the error (which will never
happen) or declare it as raisable, which in turn may require also marking
10+ function/method calls as "raises DivByZero". Both options aren't great.
And even if there was no assertion about the divisor, maybe the developer's
intent is exactly to ignore that case as an "implicit assertion" - meaning
instead of explicitly asserting the divisor value themselves (through
assert($divisor !== 0)
), they rely on divide(5, $divisor)
doing that
implicitly for them. If the assert()
fails, then nobody is expected to
really handle that assertion error; it usually bubbles up to the global
exception handler which takes care of it. If the divide()
fails on the
other hand, checked exceptions would require all the callers to actually
"check" it by catching or declaring the caller function as raises DivByZero
, but this doesn't bring any benefit to the developer in this
case.
So I assume this is why Java developers hate checked exceptions and why
Kotlin doesn't have them. I'm not aware of other implementations of checked
exceptions; there may be other, better versions of them. If you have any in
mind that overcome the issues above, I'd be interested to look into them :)
Thanks for suggestion about assert()
use cases, i know it exists before,
but never used.
Usually if something shouldn't happen i throw exception there, or at least
type control. Its like manual mark for the future - you doing something
wrong, fix it.
Error collection case its about "you're right, but external system doesnt
think so".
Example 1:
i send correctly and valid first name (for my system) to external system.
That system reported thats name contains character outside its regular
expression. I can solve it in-place creating function with regular
expression that change invalid chars to question marks and case will be
covered.
Example 2:
My system result is less than zero. Remote system expects greater than
zero. I cant just validate/sanitize data to greater than zero. If i send
single operation - it could be the exception. But there could be two cases
- multiple operations (commands) queue or bulk operation (one command, few
rows of different data). In first case i have to stop all operation,
exception could help me. In second case same error becomes to warning, and
one row should not stop whole process, but have to report me. So previously
i solved it with exception, and now exception shots my legs. Thats why i
use exceptions only if something wrong on developer/user side, and never to
system-system cases. if() still powerful.
ср, 7 февр. 2024 г. в 03:55, Alex Wells autaut03@gmail.com:
On Tue, Feb 6, 2024 at 7:14 PM Larry Garfield larry@garfieldtech.com
wrote:These two samples are logically identical, and even have mostly the
same
performance characteristics, and both expose useful data to static
analyzers. They're just spelled differently. The advantage of the
second
is that it could be implemented without generics. (ADTs would be an
optional nice-to-have.) And if the caller doesn't handle DivByZero, it
would try to pass it up to its caller, but being checked it would require
the caller to also declare that it can raise DivByZero.Let's assume that the developer knows the divisor isn't 0 - through an
assertion or anif
clause above the call todivide(5, $divisor)
. In
this case, DivByZero error cannot ever be thrown (or risen), but the
developer would still have to either handle the error (which will never
happen) or declare it as raisable, which in turn may require also marking
10+ function/method calls as "raises DivByZero". Both options aren't great.And even if there was no assertion about the divisor, maybe the developer's
intent is exactly to ignore that case as an "implicit assertion" - meaning
instead of explicitly asserting the divisor value themselves (through
assert($divisor !== 0)
), they rely ondivide(5, $divisor)
doing that
implicitly for them. If theassert()
fails, then nobody is expected to
really handle that assertion error; it usually bubbles up to the global
exception handler which takes care of it. If thedivide()
fails on the
other hand, checked exceptions would require all the callers to actually
"check" it by catching or declaring the caller function asraises DivByZero
, but this doesn't bring any benefit to the developer in this
case.So I assume this is why Java developers hate checked exceptions and why
Kotlin doesn't have them. I'm not aware of other implementations of checked
exceptions; there may be other, better versions of them. If you have any in
mind that overcome the issues above, I'd be interested to look into them :)
--
+375 (29) 676-48-68 <+375296764868> / Mobile - предпочитаемый способ связи
https://t.me/gzhegow / https://t.me/%2B375296764868 / Telegram
6562680@gmail.com
On Tue, Feb 6, 2024 at 7:14 PM Larry Garfield larry@garfieldtech.com
wrote:These two samples are logically identical, and even have mostly the same
performance characteristics, and both expose useful data to static
analyzers. They're just spelled differently. The advantage of the second
is that it could be implemented without generics. (ADTs would be an
optional nice-to-have.) And if the caller doesn't handle DivByZero, it
would try to pass it up to its caller, but being checked it would require
the caller to also declare that it can raise DivByZero.Let's assume that the developer knows the divisor isn't 0 - through an
assertion or anif
clause above the call todivide(5, $divisor)
. In
this case, DivByZero error cannot ever be thrown (or risen), but the
developer would still have to either handle the error (which will never
happen) or declare it as raisable, which in turn may require also marking
10+ function/method calls as "raises DivByZero". Both options aren't great.And even if there was no assertion about the divisor, maybe the developer's
intent is exactly to ignore that case as an "implicit assertion" - meaning
instead of explicitly asserting the divisor value themselves (through
assert($divisor !== 0)
), they rely ondivide(5, $divisor)
doing that
implicitly for them. If theassert()
fails, then nobody is expected to
really handle that assertion error; it usually bubbles up to the global
exception handler which takes care of it. If thedivide()
fails on the
other hand, checked exceptions would require all the callers to actually
"check" it by catching or declaring the caller function asraises DivByZero
, but this doesn't bring any benefit to the developer in this
case.So I assume this is why Java developers hate checked exceptions and why
Kotlin doesn't have them. I'm not aware of other implementations of checked
exceptions; there may be other, better versions of them. If you have any in
mind that overcome the issues above, I'd be interested to look into them :)
Re assertions: The problem with assertions is they can be disabled. They're really only useful as an extra "extended type check", and then only in dev. That makes them unreliable, so using them for flow control is right out. And in practice they just turn into exceptions anyway (or Throwables at least), so there's really no benefit over just using a Throwable if you're going to insist they aren't disabled for the code to work.
The Joe Duffy article I linked above describes the issues with Java's exception design. Mainly, it's only mostly-checked. It forces you to declare your throwables... but certain types of throwables don't need to be declared, which means as a consumer of a function, you have no guarantee that a function that has no declared throws will actually never throw. So you get all the pain, none of the gain. (This is admittedly a challenge for introducing them to PHP as well, which is why I am proposing a separate syntax from exceptions since they would serve a different purpose.)
As discussed in the article, Midori (the experimental language for which Duffy was tech lead) had checked exceptions that worked essentially as I have proposed here. What made them work is
- They were very lightweight.
- They were firmly and strictly checked, without any "holes" in the design like Java.
- They were used locally, as an unwrapped Either monad, rather than for up-the-stack communication.
- Midori had a much more robust type checker than Java, so more errors could be moved to the type system and eliminated entirely.
- The built-in type hierarchy was more sensible than Java's.
- Midori has guards, which eliminate 99% of cases. It's essentially promoting assertion-esque type checking into the function signature. That is, DivByZero wouldn't even be an exception, it would be a runtime enforced type error. I'd love to have these, too, but that's not the topic right now. :-)
Guards would look something like this (using Midori-inspired syntax):
function divide(float $a, float $b): float require $b !== 0 ensures return != INF
{
// ...
}
(In Midori, those could either be materialized into code or compiled away if the compiler could guarantee they held true. In PHP I don't think we could compile them away so they'd have to be materialized, but it would make them more apparent to static analyzers as well as better communicate intent to other developers.)
The article goes into much more detail, and I really do encourage reading it.
To your specific question about prior knowledge (eg, non-zero), there's a couple of ways, conceptually, to address that.
- A more robust type system can handle things like non-zero-int or unsigned-int as a type. (I believe Midori has this, but honestly it's unlikely for PHP.)
- Guard clauses.
- Better syntax making handling "no op errors" easier.
For the third, just to spitball:
function divide(float $a, float $b): int raises DivByZero
{
if ($b === 0) raise new DivByZero();
return new $a/$b;
}
$result = try divide(5, 0) on DivByZero null;
// Equivalent to:
try {
$result = divide(5, 0);
} catch (DivByZero) {
$result = null;
}
But the basic point is that DivByZero is probably a bad use case example as that should be an Abandonment case (ie, type failure), just the easiest one I came up with on the spot. :-)
To use the more practical example,
findProduct(int $id): Product raises ProductNotFound {
}
function mycontroller(int $id) {
$product = try findProduct($id) on ProductNotFound return view('not_found');
return view('product', $product);
}
If you're 100% certain the ID is valid, you could do "on ProductNotFound null" as a no-op case. However, I suspect in practice that is a minority case.
Routing would probably be more like this:
function findRoute(Request $request): Route raises RoutingError
try $route = findRoute($request);
catch (RouteNotFound $r) {
// Do stuff.
} catch (MethodNotAllowed $r) {
// Do stuff.
}
// ...
(Though in fairness, I'd probably use a proper monad for routing instead anyway.)
--Larry Garfield
I still don't understand why the problem is signature and moving a simple
throw statement to return type, and then rewriting the catch statement to
oneline-r.
I am completely satisfied with the php way of working with method
signatures except non-critical cases:
- still no
undefined
type, so some functions have limited functionality
withfunc_num_args()
inside - no languages still implement emptiness check on argument types. empty
string should always be additionally asserted, like positive-negative
numbers, and nan/inf with float stuff (nan/inf is too rarely case) - I'd say generics support but it is fully covered with phpdoc. use
argument Generic, mark it as class-string and @template and it works well
Try/catch statements don't scare me.
I feel the trouble in missing try/catch statements and requirements to
check all method call places once you throw a non-critical error just
because another way, sorry, necessary way, is too hard for fast
implementation. That trouble arrives only when you move from single-task
script to multi-task script. The nearest example is SQL, which always works
with rows and never returns one value, response is always "list of rows".
So the primary method of error handling should be focused on batch
processing, but throw
keyword is very handy for filters, assertions,
validators and low level static functions.
If you finish your code fully covered with exceptions - once you try to run
several times the same with try/catch over method calls - the original
action code could have been better by catching some exceptions on its own.
However, it is closed to you and now if you want to break it into two
parts, you will have to rewrite it completely, because it is completely
closed to you and the slightest exception that was not caught in itself
(and not in you) will break it entirely, and will not disable part of it.
The exception was invented in order to shift the closure of the problem to
the next level of developers. However, it is the exception that prevents
these developers from changing the source logic of the code that throws it
and continuing to perform the action intended in the source code - the
exception simply breaks the entire branch (following encapsulation
). This
means it should break only the most primitive functions that can be
replaced quickly. This is where the so-called SOLID came from, which forces
everything to be broken down into molecules instead of first closing the
problem and then deciding whether to break it into parts or leave it that
way.
On Thu, Feb 8, 2024 at 2:29 AM Григорий Senior PHP / Разработчик Web
6562680@gmail.com wrote:
I still don't understand why the problem is signature and moving a simple
throw statement to return type, and then rewriting the catch statement to
oneline-r.
Sorry for answering not directly to the citation. But from my personal
opinion, a static analyzer like PHPStan with the highest level of
assertion will show you the vast majority of issues in the code,
potential errors, warnings, etc. Just feed your code to it and fix it.
Why do you need to collect errors and warnings on every API call? Edge
cases are called edge because happen rarely and you can react to them
successfully with proper error monitoring. This looks like a bad
design of the app and of the monitoring tools that you propose to
"fix" with bandaids in the language.
This is where the so-called SOLID came from, which forces
everything to be broken down into molecules instead of first closing the
problem and then deciding whether to break it into parts or leave it that
way.
This is called not SOLID, this is called a good application
architecture. When an application is designed properly, the delivery
speed is always slow in the beginning. But later it allows much speedy
deliveries because developers don't need to refactor large parts of it
on every new feature. And don't need to rely on bandaids in the
language to save their butt from burning because of working with
shitty architecture.
It is always easy to write down some piece of shit just to close
tickets and fulfill initial requirements and don't care. But I still
don't understand why you keep asking the PHP language community to do
your job and fix your application instead of you?
--
Best, Alexander.
JIT I've had this solved, not only the application, but a way to.
Am sharing my experience without labels "bad", "cood", "truth" or
"correct", like you did.
Your arguments are based on your principles and I don't see in your words
any point to discuss, so - use pm or share here secret of error-free code,
if you have ideas how to solve "my" application if "you know what is the
correct design".
I think the people who use labels usually never share their experience.
On Thu, Feb 8, 2024 at 4:02 PM Григорий Senior PHP / Разработчик Web
6562680@gmail.com wrote:
JIT I've had this solved, not only the application, but a way to.
Am sharing my experience without labels "bad", "cood", "truth" or "correct", like you did.
Your arguments are based on your principles and I don't see in your words any point to discuss, so - use pm or share here secret of error-free code, if you have ideas how to solve "my" application if "you know what is the correct design".
I think the people who use labels usually never share their experience.
I already shared my experience - to use PHPStan with the highest level
of assertion to catch a lot of errors, warnings, and possible issues
in the code and fix them before the code even runs on a server. Did
you read my message? Did you try PHPStan? If you tried, what was the
result? Please share it.
When you say SOLID prevents from solving business needs quickly, you
should know that business always needs everything yesterday. The
problem is not in SOLID or PHP language. The problem is in a bad
design/architecture that led to a lot of errors and warnings in
unpredictable places that require super custom collection of them on
EVERY request. Properly designed applications do not require
language-level changes to fulfill business requirements and monitor
edge cases.
If you have a super legacy code, why the language should care about
it? Is it the purpose of the language? Just drop this legacy code if
it requires custom error and warning collection. Explain to the
business that the code state is so weird that it requires a lot of
time to refactor or to reimplement it from scratch. This is a very
common problem of legacy rubbish code and it is not specific to the
PHP language.
Switching the discussion to a flame with personal offense is not a
good style here, please don't do this.
Ok, I understand your position. Want explanations about personal behavior -
please write by telegram.
PHPStan is out the case.
SOLID is not a marker of good architecture, it's a marker of idealistic
understanding, for me.
I already long ago passed that time where I can just say "this code is bad,
I am leaving". New rules of business, and then, payments - there's no bad
code, there's bad coders. And also "bad coders have to be replaced with AI"
(especially once we understand that AI helps only if you already know the
answer, they don't solve anything, but use any hype to hit employees).
So language should decide - help developers or help businesses. The top of
any business = do nothing. That's why business is on the wrong side related
to science.
Future of any language is predicted not with code beauty, but with payments
in the business. Am sure, you're noticed that hard things live longer than
easy things. That's a fenomen, that has explanations. Employees prefer easy
jobs, businesses prefer hype. Hype requires confusion, controversy. So it's
the opposite. Employees want to get more money for the job, businesses want
to pay less.
Pricing increases once things become more difficult, but I am asking to
simplify something. From that position I am asking for bad things.
At the same time, I am asking to help most people do their job more easily,
to solve more tasks and continue having a job. Just because that clever
owners will fire out all the people until war clears it out, clears all of
us (they survive, because they have money).
This is the story of how the strongest and tricky win over the clever and
smart. That's why discussions always hit the wall "why we should". You, or
language, or community, don't owe anyone anything. Times required from
languages make things simpler and at the same time make hype more complex
and difficult. One part is needed by employees, the second part must be
done for employers.
If you make things simpler - it moves tape to employees, but business
owners will select more difficult solutions and more legacy, because they
dont know bad
and good
, they know only hype. Simple things never create
hype. That's the mandatory point.
Btw, these explanations are very simplified and funny to declare them here.
On Thu, Feb 8, 2024 at 6:25 PM Григорий Senior PHP / Разработчик Web
6562680@gmail.com wrote:
PHPStan is out the case.
May I know why is it out?
I'm actively using it in my projects and it helps me a lot to not
relax and write safe code. It also helps a lot in cleaning up legacy
code. Sometimes I find places in the legacy code where the logic is
too hard to understand in a reasonable amount of time, but they're
rare. These places can be enveloped with additional logging with no
problem.
I'm referring to your initial use case about collecting errors and I'm
suggesting a good tool to solve it. You may start from lower assertion
levels in PHPStan and gradually increase the level to be not ruined by
tons of errors at a time. Of course, I don't know your situation. But
what I do in such cases - is explain to the business the problem and
what steps are required to improve the situation. So the business is
aware of it, the business should not have false impressions about the
code and false expectations from me. Just prefer to be open and tell
the truth. Not prefer to be a yes-man and promise speedy magic
solutions. I'm a professional developer and if I'm asked to do some
crappy coding regularly, I would prefer to leave such a job. My
selling point is to produce high-quality well-architected code.
On Thu, Feb 8, 2024 at 6:25 PM Григорий Senior PHP / Разработчик Web
6562680@gmail.com wrote:PHPStan is out the case.
May I know why is it out?
I'm actively using it in my projects and it helps me a lot to not
relax and write safe code. It also helps a lot in cleaning up legacy
code. Sometimes I find places in the legacy code where the logic is
too hard to understand in a reasonable amount of time, but they're
rare. These places can be enveloped with additional logging with no
problem.I'm referring to your initial use case about collecting errors and I'm
suggesting a good tool to solve it. You may start from lower assertion
levels in PHPStan and gradually increase the level to be not ruined by
tons of errors at a time. Of course, I don't know your situation. But
what I do in such cases - is explain to the business the problem and
what steps are required to improve the situation. So the business is
aware of it, the business should not have false impressions about the
code and false expectations from me. Just prefer to be open and tell
the truth. Not prefer to be a yes-man and promise speedy magic
solutions. I'm a professional developer and if I'm asked to do some
crappy coding regularly, I would prefer to leave such a job. My
selling point is to produce high-quality well-architected code.--
To unsubscribe, visit: https://www.php.net/unsub.php
If I understood references correctly, it's actually simple - they work at
an outsourcing company of some sorts that has a project conveyor - 13
projects in paralel was mentioned. And clients refuse to pay to do things
right or do upkeep/maintenance on their assets and so on. That's why it's
easier to ask for a feature here than do the work themselves on the
applications they are supporting. Nobody cares except the developers who
have to deal with it and are being told to fix it somehow or get fired.
Just because PHPStan is a ready solution for design errors that you can
predict.
My case is about errors that you detect later, after the design is
finished, PHPStan shows you that "all done!".
Because it's not \LogicExceptions (types, mappings), it's all
\RuntimeExceptions... You have to implement it after the design is done!
Also writing code comments for PHPStan accidentally increases time of
catching \LogicExceptions that will be autocatched once an exception
happens and is reported. This is necessary work, but it increases the
estimate; the business does not want to hear long deadlines, and considers
you ineffective. But if you say too small, the code will not even pass
PHPStan, it will not even pass visual inspection. Therefore, I say some
magical things, and then I correct them, for some reason they like this
approach better, they are not used to improving until everything works
properly, they even push the word into the lexicon - MVP. This means “it
doesn’t work well but it works”, it seems normal to them in order to get
results faster. Because they will receive bonuses for results, but for bad
results you will receive punishment.
A closer solution is "get out from the job where business was built that
way to ignore complainings". I had worked for 12 years. I've changed a lot
of jobs because of that. And any new job is the same. It could be more or
less ready to build the same, but they are working on it. It's not a
solution either. As a result - "you cant rebuild
non-yours successful business that gives you money because you know how
things work", you have to fix or lose your rent home.
===
@Arvids Godjuks arvids.godjuks@gmail.com
I had to work in such companies, and there were 13 projects at the moment.
But after some time I realized that nothing depends on the number of
projects. All businesses strive to not give away the right to think to
their employees. If this happened, it was more an accident than a goal. If
they are forced to listen to you, you are considered a problem that they do
not yet know how to solve.
Now my company has only one project, which they are leading themselves. But
business and profit dictate the conditions - at first we do a year very
quickly, then we redo it for 3 months, which we will have time, but the
amount of legacy is too large, because the pace is insane, and the tasks
are becoming more and more difficult.
They can't just "wait quietly until you finish." They will ask you when,
and will be disappointed if you suddenly suddenly realized that you need to
move the deadlines, because there are already 5 new ones behind the current
task and they are planned for the quarter. “Rewrite better” is not included
in them.
Let's stop discussing my personality, I gave a rationale for why languages
should preferably help developers, since such a question arose. Because
there won’t be fewer situations like mine, but there will be more jobs. And
there will be a lot more bad companies, and in the end there will be very
bad ones left.
Just because PHPStan is a ready solution for design errors that you can
predict.
My case is about errors that you detect later, after the design is
finished, PHPStan shows you that "all done!".Because it's not \LogicExceptions (types, mappings), it's all
\RuntimeExceptions... You have to implement it after the design is done!
Also writing code comments for PHPStan accidentally increases time of
catching \LogicExceptions that will be autocatched once an exception
happens and is reported. This is necessary work, but it increases the
estimate; the business does not want to hear long deadlines, and considers
you ineffective. But if you say too small, the code will not even pass
PHPStan, it will not even pass visual inspection. Therefore, I say some
magical things, and then I correct them, for some reason they like this
approach better, they are not used to improving until everything works
properly, they even push the word into the lexicon - MVP. This means “it
doesn’t work well but it works”, it seems normal to them in order to get
results faster. Because they will receive bonuses for results, but for bad
results you will receive punishment.A closer solution is "get out from the job where business was built that
way to ignore complainings". I had worked for 12 years. I've changed a lot
of jobs because of that. And any new job is the same. It could be more or
less ready to build the same, but they are working on it. It's not a
solution either. As a result - "you cant rebuild
non-yours successful business that gives you money because you know how
things work", you have to fix or lose your rent home.===
@Arvids Godjuks arvids.godjuks@gmail.com
I had to work in such companies, and there were 13 projects at the moment.
But after some time I realized that nothing depends on the number of
projects. All businesses strive to not give away the right to think to
their employees. If this happened, it was more an accident than a goal. If
they are forced to listen to you, you are considered a problem that they do
not yet know how to solve.Now my company has only one project, which they are leading themselves. But
business and profit dictate the conditions - at first we do a year very
quickly, then we redo it for 3 months, which we will have time, but the
amount of legacy is too large, because the pace is insane, and the tasks
are becoming more and more difficult.They can't just "wait quietly until you finish." They will ask you when,
and will be disappointed if you suddenly suddenly realized that you need to
move the deadlines, because there are already 5 new ones behind the current
task and they are planned for the quarter. “Rewrite better” is not included
in them.Let's stop discussing my personality, I gave a rationale for why languages
should preferably help developers, since such a question arose. Because
there won’t be fewer situations like mine, but there will be more jobs. And
there will be a lot more bad companies, and in the end there will be very
bad ones left.
OK, there's multiple, really unrelated things going on here. Let's cut to the chase:
It sounds like the root problem is not code, but management. The OP's management is insisting "make change X to a bad codebase, but we'll give you 1/10 as much time as doing it properly actually needs." That is a social problem, not a technical problem. No software will solve a social problem. In this case, the social problem is "your manager is an abusive moron." Sorry, PHP cannot help you with that, no matter what feature we add.
Moreover, adding features to the language to make it easier to write sloppy code that will be harder for the next person to maintain is a losing proposition. It will just compound on itself until both the language and applications written in it are universally trash. PHP has spent 20 years digging out of "quick and easy" design decisions that were made back in the 90s that are still causing problems today. I do not see any appetite for adding more such structurally-poor features.
There may be an appetite for adding features that make it easier to write good code that is self-correcting. We can (and do) debate what features qualify for that description, and whether my checked exceptions proposal qualifies for that, but that's independent of "my boss is asking the impossible." It's not the language's job to solve for bad bosses.
I think what you're looking for (since your descriptions are not particularly precise) is a customizable version of json_last_error()
and friends, which operates on global state. I am only one vote, of course, but based on prior discussions here I don't believe there will be any support whatsoever for a global-state-based error mechanism in PHP, ever. If anything the trend is to move away from that, in part because it makes "hack random shit into random crappy code with more globals" harder. That's a good thing.
If that's the case, we should probably just close this thread and potentially move the checked exceptions discussion to a new thread, if anyone else is interested in continuing it. But the original request here is, if I am understanding it, dead on arrival. No amount of talk about bad bosses with poor expectations will change that.
--Larry Garfield
From that point of view you're absolutely correct.
I want to use json_last_error()
with global state, but with the ability to
manually change the state size to prevent memory overflow.
So I suggested it because the count of bad code will grow in arithmetic
progression because of the times on the Earth - firing out developers who
"know the truth", hiring cheap developers who "just do".
You could believe in correctness or help people who need that help.
I understand that php spent many years on fast solutions, and btw, I am
glad to write code with PHP, even with older versions >5.6 / >7.2, because
there's exactly catched balance between "all possible" and "you need to".
This raise
is a case that will be required until somebody changes the
business world. But business never changes, in the last 30 years it has
become more "predatory" and unscrupulous.
Getting back to the error handling problem - still no solution about
"exceptions coming to warnings being called in batches/pipes/bulks" and no
solution for "each exception could cause time loss on big amounts of
incoming data".
If you aren't gonna implement raise
keyword (or other way like that) - at
least one thing could be implemented easily - collecting error traces only
if it is required, some mark, or flag, if you don't want to implement
different interface that won't break/collapse the code, at least by honored
SOLID separate responsibility of trace collection and error raising, to
reduce timelosses. I mean - allow the developer to throw an error that just
stops the code with a message, doing that the fastest way possible.
Otherwise, I understand the point, and have nothing to add here.